这应该被称为对象切片的一些特殊情况吗?

时间:2016-05-05 12:54:13

标签: c++ arrays inheritance terminology pointer-arithmetic

我们说我有一个班级Derived,它来自班级Base,而sizeof(Derived) > sizeof(Base)。现在,如果像这样分配一个Derived数组:

Base * myArray = new Derived[42];

然后尝试使用

访问n个对象
doSomethingWithBase(myArray[n]);

然后,由于从无效位置访问Base,这可能(但并非总是)导致未定义的行为。

此类编程错误的正确用语是什么?是否应将其视为object slicing的情况?

4 个答案:

答案 0 :(得分:26)

它根本不是切片,而是未定义的行为,因为你正在访问一个不存在的Derived对象(除非你运气好并且大小排成一行,在这种情况下它仍然是UB但可能会无论如何都有用。)

这是一个失败的指针算法的简单案例。

答案 1 :(得分:19)

这不是对象切片。

如上所述,索引myArray不会导致对象切片,但会因导入Derived数组而导致未定义的行为,就像它是Base的数组一样。

一种"阵列衰变错误"。

new Derived[42]myArray的分配时引入的错误可能是数组衰减错误的变体。

在这种类型的bug的真实例子中,有一个实际的数组:

Derived x[42];
Base *myArray = x;

引入该问题是因为Derived数组衰减为指向Derived的指针,其值等于其第一个元素的地址。衰减允许指针分配正常工作。这种衰减行为继承自C,这是一种语言设计功能,允许数组通过引用"传递。

这导致我们更糟糕地体现了这个错误。此功能为数组语法提供C和C ++语义,将数组函数参数转换为指针参数的别名。

void foo (Base base_array[42]) {
    //...
}

Derived d[42];
foo(d);          // Boom.

但是,new[]实际上是一个重载运算符,它返回指向已分配数组对象开头的指针。所以它不是数组衰减的真实实例(即使使用了数组分配器)。但是,错误症状是相同的,new[]的意图是获得Derived的数组。

检测并避免错误。

使用智能指针。

使用智能指针对象而不是管理原始指针可以避免这种问题。例如,unique_ptr的类似编码错误如下所示:

std::unique_ptr<Base[]> myArray = new Derived[42];

这会产生编译时错误,因为unique_ptr的构造函数是explicit

使用容器,也许std::reference

或者,您可以避免使用new[],并使用std::vector<Derived>。然后,您可能会强迫自己设计一个不同的解决方案,将此数组发送到仅Base识别的框架代码。可能是模板功能。

void my_framework_code (Base &object) {
    //...
}

template <typename DERIVED>
void my_interface(std::vector<DERIVED> &v) {
    for (...) {
        my_framework_code(v[i]);
    }
}

或者,使用std::reference_wrapper<Base>

std::vector<Derived> v(42);
std::vector<std::reference_wrapper<Base>> myArray(v.begin(), v.end());

答案 2 :(得分:11)

这不是任何对象切片。

C ++标准完美地定义了对象切片。它可能违反了面向对象的设计原则或其他任何原则,但它并不违反C ++规则。

此代码违反了5.7 [expr.add] paragraph 7

  

对于加法或减法,如果表达式PQ具有“指向 cv T的指针”,其中T不同从cv-unqualified数组元素类型,行为是未定义的。 [注意:特别是,当数组包含派生类类型的对象时,指向基类的指针不能用于指针算术。 - 注意事项]。

数组下标运算符被定义为等效于指针算术5.2.1 [expr.sub] paragraph 1

  

表达式E1[E2]*((E1)+(E2))

相同(根据定义)

答案 3 :(得分:8)

这不是切片的情况,尽管它非常相似。切片定义明确。由于非法指针算法,这只是未定义的行为(总是,不仅仅是可能)。