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


当前回答

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

#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'

其他回答

如果您有一个基类a和一个派生类B,那么您可以执行以下操作。

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

现在,方法wantAnA需要派生的副本。然而,派生的对象不能完全复制,因为类B可以创建不在其基类A中的其他成员变量。

因此,要调用wantAnA,编译器将“切片”派生类的所有其他成员。结果可能是您不想创建的对象,因为

它可能是不完整的,它的行为类似于A对象(B类的所有特殊行为都丢失了)。

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

例如

class A {
   int foo;
};

class B : public A {
   int bar;
};

因此,类型B的对象有两个数据成员,foo和bar。

那么如果你要写这个:

B b;

A a = b;

然后b中关于成员栏的信息在a中丢失。

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

简单的答案是,通过按值将派生对象分配给基础对象来切片对象,即,剩余对象只是派生对象的一部分。为了保持值语义,切片是一种合理的行为,其用途相对较少,这在大多数其他语言中都不存在。有些人认为它是C++的一个特性,而许多人则认为这是C++的怪癖/错误特性之一。

在我看来,除了你自己的类和程序架构/设计不好之外,切片并不是什么问题。

如果我将一个子类对象作为参数传递给一个方法,该方法接受一个超类类型的参数,那么我当然应该意识到这一点,并且知道在内部,被调用的方法只能使用超类(也称为基类)对象。

在我看来,提供一个请求基类的子类会导致子类特定的结果,这似乎只是一个不合理的期望,会导致切片成为一个问题。它要么在方法的使用上设计糟糕,要么子类实现糟糕。我猜这通常是牺牲了良好的OOP设计,而为了方便或提高性能的结果。

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