代码之家  ›  专栏  ›  技术社区  ›  gmhk

对象切片,它有优势吗?

  •  5
  • gmhk  · 技术社区  · 15 年前

    对象切片是指当一个子类被分配给基类时,对象会失去一些属性或函数。 有些事

    Class A{
    
    }
    Class B extends A{
    
    }
    
    Class SomeClass{
    A a = new A();
    B b = new B();
    
    // Some where if might happen like this */
    a = b; (Object slicing happens)
    
    }
    

    我们说物体切片有什么好处吗? 如果是,请告诉我对象切片在开发中有何帮助,以及在何处可能有帮助?

    1 回复  |  直到 11 年前
        1
  •  21
  •   Steve Jessop    15 年前

    在C++中,应该将对象切片视为 转换 从派生类型到基类型[*]。一个全新的对象被创造出来,这是“一个真实故事的启发”。

    有时这是你想做的事情,但结果在任何意义上都不是原来的对象。当对象切片出错时,就是人们不注意的时候,认为它是同一个对象或它的副本。

    这通常是不有益的。事实上,这通常是意外的,当某人通过值时,他们打算通过引用。

    很难想出一个例子,说明何时切片是正确的事情,因为很难(特别是C++)提出一个非抽象基类是绝对正确的例子。这是一个重要的设计点,不能轻易忽略——如果您发现自己有意或无意地切割了一个对象,很可能您的对象层次结构是错误的。要么基类不应用作基类,要么它至少应具有一个纯虚拟函数,因此不能按值进行切片或传递。

    所以,我举的任何一个例子,如果一个对象被转换成它的基类对象,都会引起反对,“等等,你从一个具体的类继承了什么?”如果切片是偶然的,那么它可能是一个bug,如果是故意的,那么它可能是“代码气味”。

    但答案可能是“是的,好的,这个 不应该 真的是事情的结构,但考虑到它们 这样的结构,我需要从派生类转换为基类,并且根据定义,这是一个切片”。本着这种精神,这里有一个例子:

    struct Soldier {
        string name;
        string rank;
        string serialNumber;
    };
    
    struct ActiveSoldier : Soldier {
        string currentUnit;
        ActiveSoldier *commandingOfficer; // the design errors multiply!
        int yearsService;
    };
    
    template <typename InputIterator>
    void takePrisoners(InputIterator first, InputIterator last) {
        while (first != last) {
            Soldier s(*first);
            // do some stuff with name, rank and serialNumber
           ++first;
        }
    }
    

    现在,要求 takePrisoners 函数模板是其参数是可转换为soldier的类型的迭代器。它不必是派生类,我们也不直接访问成员“name”等,因此 俘虏 已经尝试提供最容易实现的接口,考虑到这些限制(a)应该与士兵一起使用,(b)应该可以编写它也可以使用的其他类型。

    activesoldier是另一种类型。出于仅为该类的作者所知的原因,它选择公开地从士兵继承,而不是提供重载的转换运算符。我们可以争论这是否是一个好主意,但假设我们一直坚持下去。因为它是派生类,所以可以转换为士兵类。这种转换称为切片。因此,如果我们打电话 俘虏 路过 begin() end() 迭代器是一个activesoldiers向量,然后我们将它们切片。

    您可能会为一个输出器想出类似的例子,在这个例子中,接收者只关心被传递的对象的基类部分,因此允许在将对象写入迭代器时对它们进行切片。

    它的“代码气味”是因为我们应该考虑(a)重写activesoldier,和(b)更改士兵,以便使用函数而不是成员访问来访问它,这样我们就可以将这组函数抽象为其他类型可以独立实现的接口,以便 俘虏 不必变成士兵。这两种方法中的任何一种都可以消除对切片的需求,并且对于我们的代码在将来可以轻松地扩展具有潜在的好处。

    因为它是一个。下面最后两行是做同样的事情:

    struct A {
        int value;
        A(int v) : value(v) {}
    };
    
    struct B : A {
        int quantity;
        B(int v, int q) : A(v), quantity(q) {}
    };
    
    int main() {
        int i = 12;  // an integer
        B b(12, 3);  // an instance of B
        A a1 = b;    // (1) convert B to A, also known as "slicing"
        A a2 = i;    // (2) convert int to A, not known as "slicing"
    }
    

    唯一的区别是(1)调用的是A的复制构造函数(编译器提供的,即使代码不提供),而(2)调用的是A的int构造函数。

    正如其他人所说的,Java不做对象切片。如果您提供的代码被转换为Java,则不会发生任何对象切片。Java变量是引用而不是对象,所以后置条件是 a = b 只是变量“a”引用的对象与变量“b”相同——通过一个引用进行的更改可以通过另一个引用看到,依此类推。它们只是用不同的类型来引用它,这是多态性的一部分。一个典型的类比是,我可能认为一个人是“我的兄弟”[**],而其他人可能认为同一个人是“我的牧师”。相同的对象,不同的接口。

    您可以使用指针或引用在C++中获得类似Java的效果:

    B b(24,7);
    A *a3 = &b; // No slicing - a3 is a pointer to the object b
    A &a4 = b;  // No slicing - a4 is a reference to (pseudonym for) the object b
    

    事实上,我哥哥不是牧师。