我们说我有一个班级Derived
,它来自班级Base
,而sizeof(Derived) > sizeof(Base)
。现在,如果像这样分配一个Derived
数组:
Base * myArray = new Derived[42];
然后尝试使用
访问n
个对象
doSomethingWithBase(myArray[n]);
然后,由于从无效位置访问Base
,这可能(但并非总是)导致未定义的行为。
此类编程错误的正确用语是什么?是否应将其视为object slicing的情况?
答案 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:
对于加法或减法,如果表达式
P
或Q
具有“指向 cvT
的指针”,其中T
不同从cv-unqualified数组元素类型,行为是未定义的。 [注意:特别是,当数组包含派生类类型的对象时,指向基类的指针不能用于指针算术。 - 注意事项]。
数组下标运算符被定义为等效于指针算术5.2.1 [expr.sub] paragraph 1:
表达式
相同(根据定义)E1[E2]
与*((E1)+(E2))
答案 3 :(得分:8)
这不是切片的情况,尽管它非常相似。切片定义明确。由于非法指针算法,这只是未定义的行为(总是,不仅仅是可能)。