在c++中,什么是对象切片,什么时候发生?


当前回答

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

下面是一个示例:

#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;
}

它将产生:

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

其他回答

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

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

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

下面是一个示例:

#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;
}

它将产生:

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

谷歌第三次匹配“C++切片”给了我这篇维基百科文章http://en.wikipedia.org/wiki/Object_slicing这(很热,但前几篇文章定义了问题):http://bytes.com/forum/thread163565.html

因此,当您将子类的对象分配给超级类时。超类对子类中的附加信息一无所知,并且没有空间存储它,因此附加信息被“分割”。

如果这些链接没有提供足够的信息来获得“好答案”,请编辑您的问题,让我们知道您还需要什么。

这里的大多数答案都无法解释切片的实际问题。它们只解释了切片的良性情况,而不是不可靠的情况。与其他答案一样,假设您处理的是两个类A和B,其中B(公开)来自A。

在这种情况下,C++允许您将B的实例传递给A的赋值运算符(以及复制构造函数)。这之所以有效,是因为B的实例可以转换为常量a&,这是赋值运算符和复制构造函数希望它们的参数是什么。

良性病例

B b;
A a = b;

没有什么不好的事情发生——你要求A的实例是B的副本,这正是你得到的。当然,a不会包含b的一些成员,但它应该怎么做?毕竟,它是A,而不是B,所以它甚至没有听说过这些成员,更不用说能够存储它们了。

背信弃义的案子

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

你可能会认为b2会是b1的复制品。但是,唉,这不是!如果你检查它,你会发现b2是一种弗兰肯斯坦生物,由b1的一些块(B从a继承的块)和b2的一些块组成(只有B包含的块)。哎哟

怎么搞的?默认情况下,C++不会将赋值运算符视为虚拟运算符。因此,行a_ref=b1将调用a的赋值运算符,而不是B的赋值运算符。这是因为,对于非虚拟函数,声明的(形式上:静态)类型(即a&)决定调用哪个函数,而不是实际的(形式:动态)类型(由于a_ref引用了B的实例,因此将是B)。现在,A的赋值运算符显然只知道A中声明的成员,因此它将只复制那些成员,而保留B中添加的成员不变。

解决方案

只分配给对象的一部分通常意义不大,但不幸的是,C++没有提供内置的方法来禁止这种情况。不过,你可以自己动手。第一步是使赋值运算符虚拟化。这将确保调用的始终是实际类型的赋值运算符,而不是声明的类型。第二步是使用dynamic_cast验证指定的对象是否具有兼容类型。第三步是在(protected!)成员assign()中进行实际赋值,因为B的assign(()可能希望使用a的assign)复制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
  }
};

注意,为了方便起见,B的运算符=协变地重写返回类型,因为它知道它返回的是B的一个实例。

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

说明:考虑以下类声明:

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

由于基类复制函数不知道派生的任何信息,因此只复制派生的基部分。这通常被称为切片。