【问题标题】:Why does slicing occur? [duplicate]为什么会发生切片? [复制]
【发布时间】:2014-10-16 21:58:36
【问题描述】:

请考虑以下代码:

Base b;
if (something)
    b = DerivedA();
else
    b = DerivedB();

众所周知,在这种情况下,会发生“切片”:在 C++ 中,我们不能将基类型的变量分配给派生类型的对象;该对象将被“切割”掉任何未在基本类型中定义的东西。 (如果我们想做这样的事情,我们必须使用指针或引用)。

我想了解造成这种情况的实际原因。即,Base 变量在不切片的情况下无法容纳 Derived 对象的原因。

我的假设是,原因是 Base 对象和 Derived 对象的大小可能不同,因此我们不能保证能够存储整个 Derived 对象在Base 变量中。 Base 可能占用 4 个字节,而 Derived 占用 7 个字节。所以我们决定总是对派生对象进行切片以适应基本类型的大小。

我们能够使用指针来做到这一点,因为它们都占用相同数量的内存。

这个假设正确吗?如果不是,那么切片的真正原因是什么?

【问题讨论】:

  • 您自己回答问题。 7个字节不能进4个。
  • @MattMcNabb 好的,这是我的假设,想确认一下。想也许这更像是一个设计问题。顺便说一句,为什么我们不能调整变量的大小以适应分配的对象?
  • 这正是切片的作用。它只是没有按照你想要的方向调整大小。
  • "我们可以用指针来做到这一点,因为它们都占用相同数量的内存。" - 这没有任何意义。指针不一定占用相同数量的内存,但这没有实际意义,因为指针不是类类型,因此无论如何它们都不能被切片。
  • @MattMcNabb。这是完全有道理的,而且是真的:sizeof(Base *) == sizeof(DerivedA *).

标签: c++ variables object memory object-slicing


【解决方案1】:

没有。在您的示例中,切片的原因是不同的。

Base b; 行中,您在堆栈上为Base 类型的对象分配空间,并且已经调用了它的默认构造函数。因此,在if-statement 的每个分支中,会发生对b赋值,这是通过赋值运算符实现的,通常带有签名Base::operator=(const Base&)。如果您不重载此运算符,则其默认语义是逐字段复制。请注意,参数类型为Base(或const Base&),因此只有右侧的Base-字段可见!

假设您某种方式将 DerivedA 对象中包含的所有信息存储在 Base 对象中(尽管这不太可能),您可以将赋值运算符重载为 Base::operator=(const DerivedA&) ,实现你自己的赋值语义,上面的就可以完美地工作了。

【讨论】:

  • 我认为这算作切片(可以通过复制赋值运算符或复制构造函数完成)
  • 是的,但是 OP 写道:“所以我们决定始终对派生对象进行切片以适应基本类型的大小。”,而观察到的效果与自动大小截断等无关,但仅使用分配 operator= 并没有被重载以解决 DerivedA 实例的特征(正如我所说,它理论上可以)。我的理解是,问题不在于这是否是切片,而是切片的原因是什么。
【解决方案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,并且正如您所指出的,它比必要的要小,并且会截断数据。

希望这会有所帮助。

【讨论】:

  • 是的,您刚刚描述了切片。我在问它发生的技术原因:)
  • @AvivCohn 这就是技术原因。 Base 的赋值运算符(或复制构造函数,如果您的示例有点不同)不知道 Derived,也不知道它应该以某种方式将 Derived 的成员复制到自身中,或者以其他方式开始表现为 Derived。
  • 那么更准确的说法是:“切片发生是因为定义的变量的大小已设置且无法更改,因此尝试放入大于其大小的任何内容都会导致切片” ?或者:“A 类的复制构造函数接受A 类型的参数,所以它会简单地忽略不属于A 类型的任何内容”?
  • @AvivCohn - 是的,但请考虑上面的示例。您将如何调用派生的复制 ctor,或者如何让它以某种方式被填满?一个virt函数?
  • @AvivCohn 后者。您可以在 Base 中编写一个复制构造函数/赋值运算符,该运算符在 Derived 上重载,为 Derived 的副本动态分配空间,并更改 Base 中所有函数的行为,以根据 Derived 是否已分配调用委托给 Derived 的分配副本复制进来。但这将是前所未有的疯狂,这里有一个例子:ideone.com/16hLr6
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-19
  • 1970-01-01
  • 1970-01-01
  • 2015-04-07
  • 1970-01-01
  • 2019-01-14
  • 2014-05-21
相关资源
最近更新 更多