如何用派生类对象无拷贝初始化std::array<基类>?
当用B对象初始化std::array<A>的元素时,本质是对象切片操作:B作为A的派生类,初始化A类型元素时,会提取B中的基类部分,调用A的拷贝构造函数(或移动构造,取决于上下文)创建array里的A对象。你传入的B临时对象(比如B{})在完成切片初始化后会被销毁,所以能观察到析构函数调用——这个过程既有A对象的拷贝(或移动)构造,也有B临时对象的销毁开销。
另外,std::array的聚合初始化规则要求元素类型与初始化器匹配,当类型不匹配时(这里是B→A),会先通过隐式类型转换生成A的临时对象,再将这个临时对象拷贝到array的元素存储空间中,这也是拷贝操作的直接来源。
结合你的场景(B仅在构造时处理基类数据,对象大小、类型编译期已知),可以从以下几种思路实现无拷贝:
1. 提取B的构造逻辑,直接初始化A
既然B的作用只是构造时处理A的数据,完全可以把B中的处理逻辑提取成constexpr函数,直接生成A对象用于聚合初始化:
constexpr A make_processed_A(/* 构造参数 */) { A a; // 原来B构造函数中处理A数据的逻辑 a.val = ...; return a; } // 无拷贝初始化std::array<A> std::array<A, 2> arr = {make_processed_A(...), make_processed_A(...)};
这种方式完全跳过B对象的创建,直接构造目标A对象,自然没有拷贝和销毁开销。
2. 让B成为A的构造器(而非派生类)
如果必须保留B的封装形式,可以把B改成专门的构造器类型,而非继承A:
struct B { constexpr A operator()(/* 参数 */) const { A a; // 处理逻辑 return a; } }; // 直接用B生成A初始化array std::array<A, 2> arr = {B{}(...), B{}(...)};
同样避免了派生类切片和临时对象销毁的问题,全程直接构造A对象。
3. 利用constexpr优化(若允许编译期计算)
如果你的数据处理逻辑是constexpr兼容的,编译器会在编译期直接生成A对象的常量值,初始化std::array时完全不会有运行时的拷贝或销毁操作——甚至连B的临时对象都不会在运行时存在。比如:
struct B : A { constexpr B(/* 参数 */) : A() { // 编译期处理基类数据 this->val = 42; } }; // 编译期完成初始化,无运行时拷贝/销毁 constexpr std::array<A, 2> arr = {static_cast<A>(B{}), static_cast<A>(B{})};
这里static_cast<A>(B{})的转换会在编译期完成,最终array直接存储编译好的A对象数据,运行时无任何额外开销。
注意:避免未定义行为的“骚操作”
不要尝试用reinterpret_cast直接把std::array<B>转成std::array<A>,哪怕两者大小相同——这属于标准未定义行为,可能在不同编译器或优化级别下出现诡异问题,完全不值得冒这个风险。
内容的提问来源于stack exchange,提问作者poby

