在存储基类的vector/array中使用placement new创建派生类对象是否合法
这段C++代码是否符合标准?
#include <iostream> #include <memory> #include <vector> struct A { virtual ~A() { std::cout << "~A()" << std::endl; } }; struct B : public A { virtual ~B() { std::cout << "~B()" << std::endl; } }; int main() { std::vector<A> a(1); a[0].~A(); :: new(std::addressof(a[0])) B(); }
这段代码不符合C++标准,属于未定义行为,核心原因如下:
std::vector<A>分配的是固定存储A类型对象的连续内存,每个元素的存储空间大小为sizeof(A)。B作为A的派生类,其对象大小至少不小于sizeof(A)(即便无额外成员,也可能因对象模型细节存在差异)。在A类型的存储位置上构造B对象,若sizeof(B) > sizeof(A)会直接触发内存越界;即便大小相同,C++标准也不允许在静态类型为A的数组元素位置上构造派生类对象——数组元素类型在编译期已确定,无法通过运行时操作改变其静态类型对应的存储语义。- 即便手动调用
~A()析构了原对象,后续用placement new构造B的操作依然违反了容器元素类型的静态约束,vector的元素必须始终是A类型对象,不能替换为派生类对象。
关于你提到的编译器优化争议:
支持编译期确定元素类型、跳过动态分派的观点是合理的——因为上述替换元素实际类型的操作本身就不符合标准,属于未定义行为,编译器有权假设数组/vector的元素类型不会被非法修改,因此可以安全进行静态类型优化,避免虚函数调用的开销。
而GCC与Clang的行为差异:
GCC针对不同长度的数组表现出不同行为:当数组长度≥3时,会通过虚表调用虚析构函数;当长度<3时,则直接调用~A()(限定析构)。Clang在该场景下的行为相对一致,通常会保留虚析构的动态分派逻辑。这种差异源于编译器各自的优化策略,但无论如何,原代码本身的合法性不受编译器行为影响,始终属于未定义行为。
内容的提问来源于stack exchange,提问作者Ng Yong Xiang
相关产品推荐
相关产品推荐

