首页 文章

什么是对象切片?

提问于
浏览
638

有人在IRC中提到它作为切片问题 .

17 回答

  • 7

    “切片”是指将派生类的对象分配给基类的实例,从而丢失部分信息 - 其中一些信息被“切片”掉 .

    例如,

    class A {
       int foo;
    };
    
    class B : public A {
       int bar;
    };
    

    因此 B 类型的对象有两个数据成员 foobar .

    然后,如果你写这个:

    B b;
    
    A a = b;
    

    然后关于成员 barb 中的信息在 a 中丢失 .

  • 1

    这里的大多数答案都无法解释切片的实际问题是什么 . 他们只解释切片的良性情况,而不是危险的情况 . 假设,与其他答案一样,您正在处理两个类 AB ,其中 BA 派生(公开) .

    在这种情况下,C允许您将 B 的实例传递给 A 的赋值运算符(以及复制构造函数) . 这是有效的,因为 B 的实例可以转换为 const A& ,这是赋值运算符和复制构造函数期望它们的参数 .

    良性案例

    B b;
    A a = b;
    

    没有什么不好的事情发生了 - 你要求 A 的一个实例是 B 的副本,而这正是你得到的 . 当然, a 不会包含一些's members, but how should it? It',毕竟不是 B ,所以它甚至没有听说过这些成员,更别说能够存储它们了 .

    奸诈的案子

    B b1;
    B b2;
    A& a_ref = b2;
    a_ref = b1;
    //b2 now contains a mixture of b1 and b2!
    

    您可能认为 b2 之后将是 b1 的副本 . 但是,唉,这是 not !如果你检查它,你会发现 b2 是一个科学怪人的生物,由一些 b1BA 继承的块)和一些 b2 (仅包含 B 的块)组成 . 哎哟!

    发生了什么?好吧,默认情况下,C不会将赋值运算符视为 virtual . 因此, a_ref = b1 行将调用 A 的赋值运算符,而不是 B 的赋值运算符 . 这是因为对于非虚函数, declared 类型( A& )确定调用哪个函数,而不是 actual 类型(因为 a_ref 引用了 B 的实例,所以 B ) . 现在, A 的赋值运算符显然只知道在 A 中声明的成员,因此它只会复制那些成员,使 B 中添加的成员保持不变 .

    解决方案

    仅分配给对象的某些部分通常没什么意义,但遗憾的是,C没有提供禁止此内容的内置方法 . 但是,您可以自己动手 . 第一步是使赋值运算符成为虚拟 . 这将保证它始终是被调用的 actual 类型的赋值运算符,而不是 declared 类型 . 第二步是使用 dynamic_cast 来验证分配的对象是否具有兼容类型 . 第三步是在(受保护!)成员 assign() 中进行实际赋值,因为 Bassign() 可能会想要使用 Aassign() 来复制 A 的成员 .

    class A {
    public:
      virtual A& operator= (const A& a) {
        assign(a);
        return *this;
      }
    
    protected:
      void assign(const A& a) {
        // copy members of A from a to this
      }
    };
    
    class B : public A {
    public:
      virtual B& operator= (const A& a) {
        if (const B* b = dynamic_cast<const B*>(&a))
          assign(*b);
        else
          throw bad_assignment();
        return *this;
      }
    
    protected:
      void assign(const B& b) {
        A::assign(b); // Let A's assign() copy members of A from b to this
        // copy members of B from b to this
      }
    };
    

    请注意,为了方便起见, Boperator= 协同覆盖了返回类型,因为它返回 B 的实例 knows .

  • 4

    如果您有基类 A 和派生类 B ,则可以执行以下操作 .

    void wantAnA(A myA)
    {
       // work with myA
    }
    
    B derived;
    // work with the object "derived"
    wantAnA(derived);
    

    现在方法 wantAnA 需要 derived 的副本 . 但是,无法完全复制对象 derived ,因为类 B 可能会发明不在其基类 A 中的其他成员变量 .

    因此,要调用 wantAnA ,编译器将"slice off"派生类的所有其他成员 . 结果可能是您不想创建的对象,因为

    • 可能不完整,

    • 它的行为类似于 A -object(类 B 的所有特殊行为都丢失了) .

  • 2

    谷歌的第三场比赛"C++ slicing"给了我这篇维基百科的文章http://en.wikipedia.org/wiki/Object_slicing和这个(加热,但前几个帖子定义了问题):http://bytes.com/forum/thread163565.html

    所以当你将一个子类的对象分配给超类时 . 超类对子类中的附加信息一无所知,也没有足够的空间来存储它,因此附加信息会被“切掉” .

    如果这些链接没有为“正确答案”提供足够的信息,请编辑您的问题我们知道您还在寻找什么 .

  • 30

    这些都是很好的答案 . 我想在按值和引用传递对象时添加一个执行示例:

    #include <iostream>
    
    using namespace std;
    
    // Base class
    class A {
    public:
        A() {}
        A(const A& a) {
            cout << "'A' copy constructor" << endl;
        }
        virtual void run() const { cout << "I am an 'A'" << endl; }
    };
    
    // Derived class
    class B: public A {
    public:
        B():A() {}
        B(const B& a):A(a) {
            cout << "'B' copy constructor" << endl;
        }
        virtual void run() const { cout << "I am a 'B'" << endl; }
    };
    
    void g(const A & a) {
        a.run();
    }
    
    void h(const A a) {
        a.run();
    }
    
    int main() {
        cout << "Call by reference" << endl;
        g(B());
        cout << endl << "Call by copy" << endl;
        h(B());
    }
    

    输出是:

    Call by reference
    I am a 'B'
    
    Call by copy
    'A' copy constructor
    I am an 'A'
    
  • 7

    切片问题很严重,因为它可能导致内存损坏,并且很难保证程序不会受到影响 . 要使用该语言进行设计,支持继承的类应该只能通过引用访问(而不是通过值) . D编程语言具有此属性 .

    考虑A类,从B派生B类 . 如果A部分有一个指针p,则会发生内存损坏,而B实例将p指向B的附加数据 . 然后,当附加数据被切掉时,p指向垃圾 .

  • 29

    C中的切片问题源于其对象的值语义,这主要是由于与C结构的兼容性 . 您需要使用显式引用或指针语法来实现在执行对象的大多数其他语言中找到的“正常”对象行为,即,对象始终通过引用传递 .

    简短的答案是通过按值将派生对象分配给基础对象来切片对象,即剩余对象只是派生对象的一部分 . 为了保留 Value 语义,切片是一种合理的行为,并且具有相对罕见的用途,这在大多数其他语言中是不存在的 . 有些人认为它是C的一个特征,而许多人认为它是C的怪癖/错误特征之一 .

  • 2

    1. THE DEFINITION OF SLICING PROBLEM

    如果D是基类B的派生类,则可以将Derived类型的对象分配给Base类型的变量(或参数) .

    class Pet
    {
     public:
        string name;
    };
    class Dog : public Pet
    {
    public:
        string breed;
    };
    
    int main()
    {   
        Dog dog;
        Pet pet;
    
        dog.name = "Tommy";
        dog.breed = "Kangal Dog";
        pet = dog;
        cout << pet.breed; //ERROR
    

    尽管允许上述赋值,但分配给变量pet的值会丢失其品种字段 . 这称为 slicing problem .

    2. HOW TO FIX THE SLICING PROBLEM

    为了解决这个问题,我们使用指向动态变量的指针 .

    Pet *ptrP;
    Dog *ptrD;
    ptrD = new Dog;         
    ptrD->name = "Tommy";
    ptrD->breed = "Kangal Dog";
    ptrP = ptrD;
    cout << ((Dog *)ptrP)->breed;
    

    在这种情况下,ptrD(后代类对象)指向的动态变量的数据成员或成员函数都不会丢失 . 另外,如果需要使用函数,该函数必须是虚函数 .

  • 25

    那么......为什么丢失衍生信息不好? ...因为派生类的作者可能已经更改了表示,因此切掉额外信息会更改对象所表示的值 . 如果派生类用于缓存对某些操作更有效但对转换回基本表示而言代价高的表示,则会发生这种情况 .

    还有人认为有人还应该提到你应该做些什么以避免切片...获取C编码标准,101规则指南和最佳实践的副本 . 处理切片是#54 .

    它提出了一个有点复杂的模式来完全处理这个问题:拥有一个受保护的复制构造函数,一个受保护的纯虚拟DoClone,以及一个带有断言的公共克隆,它将告诉您(某个)派生类是否未能正确实现DoClone . (克隆方法对多态对象进行了适当的深层复制 . )

    您还可以在基本显式标记复制构造函数,如果需要,可以显式切片 .

  • -1

    在C中,派生类对象可以分配给基类对象,但另一种方法是不可能的 .

    class Base { int x, y; };
    
    class Derived : public Base { int z, w; };
    
    int main() 
    {
        Derived d;
        Base b = d; // Object Slicing,  z and w of d are sliced off
    }
    

    当派生类对象被分配给基类对象时,会发生对象切片,派生类对象的其他属性将被切除以形成基类对象 .

  • 6

    在我看来,切片并不是一个问题,除了你自己的类和程序设计不当/设计 .

    如果我将一个子类对象作为参数传递给一个方法,该方法接受类型为超类的参数,我当然应该知道并且在内部知道,被调用的方法将仅使用超类(也就是基类)对象 .

    在我看来,只有不合理的期望,提供一个子类来请求基类,以某种方式导致子类特定的结果,将导致切片成为一个问题 . 它在使用该方法时设计较差或者子类实现较差 . 我猜它通常是牺牲良好的OOP设计以支持权宜之计或性能提升的结果 .

  • 536

    好的,我会在阅读许多解释对象切片的帖子之后尝试一下,但不会知道它是如何成为问题的 .

    可能导致记忆的恶性场景腐败如下:

    • 类在多态基类上提供(意外地,可能是编译器生成的)赋值 .

    • 客户端复制并切片派生类的实例 .

    • 客户端调用访问切片状态的虚拟成员函数 .

  • 6

    在这里找到类似的答案:http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

    切片意味着当子类的对象通过值或期望基类对象的函数传递或返回时,子类添加的数据将被丢弃 .

    Explanation: 考虑以下类声明:

    class baseclass
              {
                     ...
                     baseclass & operator =(const baseclass&);
                     baseclass(const baseclass&);
              }
              void function( )
              {
                    baseclass obj1=m;
                    obj1=m;
              }
    

    由于基类复制函数不知道任何有关派生的内容,因此仅复制派生的基本部分 . 这通常称为切片 .

  • 133
    class A 
    { 
        int x; 
    };  
    
    class B 
    { 
        B( ) : x(1), c('a') { } 
        int x; 
        char c; 
    };  
    
    int main( ) 
    { 
        A a; 
        B b; 
        a = b;     // b.c == 'a' is "sliced" off
        return 0; 
    }
    
  • 0

    将Derived类Object分配给Base类Object时,派生类对象的所有成员都将复制到基类对象,但基类中不存在的成员除外 . 这些成员被编译器切掉 . 这称为对象切片 .

    Here is an Example:

    #include<bits/stdc++.h>
    using namespace std;
    class Base
    {
        public:
            int a;
            int b;
            int c;
            Base()
            {
                a=10;
                b=20;
                c=30;
            }
    };
    class Derived : public Base
    {
        public:
            int d;
            int e;
            Derived()
            {
                d=40;
                e=50;
            }
    };
    int main()
    {
        Derived d;
        cout<<d.a<<"\n";
        cout<<d.b<<"\n";
        cout<<d.c<<"\n";
        cout<<d.d<<"\n";
        cout<<d.e<<"\n";
    
    
        Base b = d;
        cout<<b.a<<"\n";
        cout<<b.b<<"\n";
        cout<<b.c<<"\n";
        cout<<b.d<<"\n";
        cout<<b.e<<"\n";
        return 0;
    }
    

    It will generate:

    [Error] 'class Base' has no member named 'd'
    [Error] 'class Base' has no member named 'e'
    
  • -2

    当派生类对象被分配给基类对象时,派生类对象的其他属性将从基类对象中切除(丢弃) .

    class Base { 
    int x;
     };
    
    class Derived : public Base { 
     int z; 
     };
    
     int main() 
    {
    Derived d;
    Base b = d; // Object Slicing,  z of d is sliced off
    }
    
  • 412

    我刚碰到切片问题并迅速降落在这里 . 所以,让我加上我的两分钱 .

    让我们从“ 生产环境 代码”中得到一个例子(或类似的东西):


    假设我们有一些可以发送行动的东西 . 例如,控制中心UI .
    此UI需要获取当前可以分派的事物列表 . 所以我们定义一个包含调度信息的类 . 我们称之为 Action . 所以 Action 有一些成员变量 . 为简单起见,我们只有2,是 std::string namestd::function<void()> f . 然后它有 void activate() ,只执行 f 成员 .

    因此UI获得了 std::vector<Action> . 想象一下一些功能:

    void push_back(Action toAdd);
    

    现在我们已经从UI的角度确定了它的外观 . 到目前为止没问题 . 但是,在这个项目上工作的其他人突然决定在 Action 对象中需要更多信息的专门操作 . 出于什么原因 . 这也可以通过lambda捕获来解决 . 此示例未从代码中取1-1 .

    所以这个家伙来自 Action 以增加他自己的味道 .
    他将自己酿造的 class 的一个实例传递给了 push_back ,但随后程序变得混乱 .

    所以发生了什么事?
    您可能已经猜到了:对象已被切片 .

    来自实例的额外信息已丢失, f 现在容易出现未定义的行为 .


    我希望这个例子能够为那些在谈论 AB 以某种方式衍生出来时无法想象的人带来光明 .

相关问题