请考虑以下代码:
Base b;
if (something)
b = DerivedA();
else
b = DerivedB();
众所周知,在这种情况下,会出现“切片”:在C ++中,我们不能将基类型的变量赋予派生类型的对象;对象将被“切片”掉基本类型中未定义的任何内容。 (如果我们想做这样的事情,我们必须使用指针或引用)。
我想了解其实际原因。即,Base
变量在不切片的情况下无法保存Derived
对象的原因。
我的假设是,原因是Base
对象和Derived
对象的大小可能不同,因此我们无法保证能够存储整个Derived
变量中的Base
个对象。 Base
可能占用4个字节,而Derived
则占用7个字节。所以我们决定总是切割派生对象以适应基本类型的大小。
我们 能够用指针执行此操作,因为它们都占用相同的内存量。
这个假设是否正确?如果没有,切片的实际原因是什么?
答案 0 :(得分:2)
问题在于复制和移动语义(复制构造函数,复制赋值等)。您将获得元素的副本,但不会复制所有元素。如果你有一个基指针,就没有问题。
如果您有一个完全填充的DerivedA对象,并将其分配给本地堆栈基类型,则将使用复制赋值,并且将丢弃所有派生元素值。
考虑何时编写复制构造函数。除了当前班级的成员之外,你还做其他工作吗?你怎么知道从当前课程中得到什么,以及做什么工作?试图这样做会非常糟糕。
class BaseType
{
private:
int m_i;
public:
explicit BaseType(BaseType const & other) // copy ctor
{
m_i = other.m_i; // bitwise copy or memberwise copy will suffer the same issue
// what else is there to do?
// BaseType has no knowledge of any other members
}
BaseType & BaseType::operator=(BaseType const & other) // copy assignment
{
m_i = other.m_i;
// what else is there to do?
// BaseType has no knowledge of any other members
}
};
即使使用按位复制(对于std::is_trivially_copyable<T>
),大小也是BaseType,并且正如您所指出的那样,小于必要值,并将截断数据。
希望这有帮助。
答案 1 :(得分:2)
没有。在您的示例中,切片的原因是不同的。
在行Base b;
中,为堆栈上的Base
类型的对象分配空间,并且已经调用其默认构造函数。因此,在if
- 语句的每个分支中,会发生什么是赋值到b
,它通过赋值运算符实现,通常带有签名{{1 }}。如果不重载此运算符,则其默认语义是逐字段副本。请注意,参数类型为Base::operator=(const Base&)
(或Base
),因此只有const Base&
- 右侧的字段可见!
假设你 以某种方式将Base
对象中包含的所有信息存储在DerivedA
对象中(虽然这不太可能),你可以重载你的赋值运算符作为Base
,实现自己的赋值语义,上面的工作完全正常。