如何在constexpr构造函数中初始化不可拷贝不可移动对象数组?
解决不可拷贝/移动且带析构函数的类数组初始化问题
这个问题其实涉及到C++不同标准里constexpr和数组初始化的几个关键限制,咱们一步步拆解来看:
为什么编译失败?
你遇到的报错主要来自两个核心原因:
- 移动构造被触发:在C++17之前,用列表初始化数组元素时,编译器会先创建临时的
A对象,再尝试把临时对象移动到数组的对应位置——但你已经显式删除了A的移动构造函数,自然会报错。 - LiteralType的限制:当
A有用户定义的非constexpr析构函数时,A就不再是LiteralType(字面量类型)。而在C++17及更早的标准里,constexpr构造函数中初始化数组时,要求数组元素必须是LiteralType,这就导致了non-constant array initialization的错误。
可行的解决方案
根据你“必须保留A的析构函数、不可移动/拷贝、constexpr构造”的需求,有几个不同方向的解决方案:
方案1:把A的析构函数改成constexpr(兼容C++17)
如果可以修改A的析构函数为constexpr,那么A会满足LiteralType的要求,同时C++17的保证拷贝消除特性会跳过临时对象的移动/拷贝步骤,直接在数组元素的内存位置构造A对象。修改后的代码如下:
class A { public: constexpr explicit A(int a) {} constexpr ~A() {} // 改为constexpr析构 A(const A&) = delete; A(A&&) = delete; }; class B { public: constexpr B() : a{A{1}, A{2}, A{3}} {} private: A a[3]; }; int main() { B b; }
用g++ -std=c++17编译就能正常通过。
方案2:升级到C++23(保留原析构函数)
C23进一步放宽了constexpr的限制,允许在constexpr上下文中使用带有非constexpr析构函数的类型,只要对象的构造过程是常量表达式。如果你的编译器支持C23(比如GCC 12及以上版本),直接用-std=c++23编译你原来的代码就行:
class A { public: constexpr explicit A(int a) {} ~A() {} // 保留原非constexpr析构 A(const A&) = delete; A(A&&) = delete; }; class B { public: constexpr B() : a{A{1}, A{2}, A{3}} {} private: A a[3]; }; int main() { B b; }
备选方案:放弃数组,改用独立成员(兼容C++11及更早)
如果既不能修改A的析构函数,也没法升级C++标准,那只能退回到你最开始的写法——把3个A对象作为独立的成员变量,而非数组:
class A { public: constexpr explicit A(int a) {} ~A() {} A(const A&) = delete; A(A&&) = delete; }; class B { public: constexpr B() : a0{1}, a1{2}, a2{3} {} private: A a0; A a1; A a2; }; int main() { B b; }
这种写法在所有支持constexpr的C++版本里都能正常编译,唯一的缺点就是失去了数组带来的遍历等便捷性。
总结
如果必须保留A的非constexpr析构函数同时使用数组,升级到C23是最直接的解决办法;如果可以修改A的析构函数为constexpr,C17就能搞定;如果两者都不行,就只能用独立成员的写法了。
内容的提问来源于stack exchange,提问作者Freddie Chopin
相关产品推荐
相关产品推荐

