C++构造类实例最佳实践:不同初始化方式的适用场景区别
首先是你给出的示例代码:
class A { private: int x; float y; public: A(int xa, float ya) { x = xa; y = ya; }; }; int main() { A a = A(1, 2); A b = {1, 2}; A c(1, 2); A d = A{1, 2}; A e{1, 2}; }
各写法差异与适用场景
A a = A(1, 2);
属于C98就支持的语法,先显式调用构造函数生成A类临时对象,再通过拷贝/移动构造初始化a。C17标准后强制要求拷贝消除,不会产生额外的拷贝/移动开销,C++17之前要求类的拷贝/移动构造函数可访问(即使实际不会调用)。
适用场景:需要显式指明构造临时对象的类型,解决初始化歧义场景。A b = {1, 2};
属于C++11引入的拷贝列表初始化,会优先匹配对应参数的构造函数,且默认禁止窄化转换(比如把超出char范围的int值传给char类型参数会直接编译报错)。如果类的对应构造函数被explicit修饰,该写法会编译失败。
适用场景:构造参数简单、无explicit限制的场景,代码简洁同时能避免隐式窄化错误。A c(1, 2);
属于C++98就支持的直接初始化,直接匹配调用对应参数的构造函数,允许窄化转换,支持调用explicit修饰的构造函数。如果类有接受std::initializer_list的构造函数,该写法不会优先匹配该类构造函数,只会匹配普通构造函数。
适用场景:传统通用初始化场景,尤其当你需要刻意避开std::initializer_list构造函数时必须用该写法(比如std::vector<int> v(10, 1)是生成10个值为1的元素,换成{}写法就会变成两个元素10和1)。A d = A{1, 2};
C11及以后支持的语法,先通过列表初始化构造A类临时对象,再拷贝/移动初始化d,同样C17之后强制拷贝消除无额外开销。相比A(1,2)构造临时对象的写法,多了列表初始化的窄化检查能力。
适用场景:需要显式指定构造类型,同时要避免窄化错误的场景。A e{1, 2};
C11引入的直接列表初始化,是目前C核心指南推荐的通用初始化写法。优先匹配接受std::initializer_list的构造函数,没有对应构造函数时再匹配普通构造函数,禁止窄化转换,支持explicit修饰的构造函数。
适用场景:绝大多数常规初始化场景都适用,仅当你需要避开std::initializer_list构造函数时替换为()写法即可。
关键特性汇总
| 写法 | C++最低版本 | 允许窄化转换 | 优先匹配initializer_list | 支持explicit构造 |
|---|---|---|---|---|
| A a = A(1,2) | C++98 | 是 | 否 | 是 |
| A b = {1,2} | C++11 | 否 | 是 | 否 |
| A c(1,2) | C++98 | 是 | 否 | 是 |
| A d = A{1,2} | C++11 | 否 | 是 | 是 |
| A e{1,2} | C++11 | 否 | 是 | 是 |
内容的提问来源于stack exchange,提问作者mada

