引言
在 C++98 中有两种变量初始化方式:直接初始化和复制初始化(拷贝初始化)。
这两种初始化方式有着明显的差异,却由于编译器的优化而变得模糊。
- 直接初始化语法形式:
objType obj(params...);
,如int x(2);
- 复制初始化语法形式:
objType obj = param;
,如int x = 2;
其中复制初始化比较适合人类的阅读习惯,但其运行机制和性能与直接初始化有很大差异。
性能方面,对于内置类型,两种初始化语法在性能上几乎没有差异,而对于用户自定义类型却可能存在较大差异(取决于用户自定义类型的布局,用户自定义类型一般是 struct
或 class
,在 C++ 中两者已经没有区别,struct
就是一个成员访问权限都为 public
的 class
)。
值得一提的是,C++11 提供了花括号初始化方法,其语法形式是
objType obj{param1, param2,......}
,其目的是统一变量初始化方法,但却和其他新特性不兼容,例如auto,模板参数类型推导等等。
直接初始化和复制初始化的区别
上文提到,两种初始化方式的差异在用户自定义类型中更加明显,因此定义一个如下一个类:
class CString
{
private:
char cs[64];
public:
// 默认构造函数
CString() {
cs[0] = '\0';
cout << "ctor: CString()" << endl;
}
// 使用C风格字符串初始化
CString(const char* pc) {
strcpy(cs, pc);
cout << "ctor: CString(const char* pc)" << endl;
}
// 复制构造函数
// CString(const CString& c) {
// strcpy(cs, c.cs);
// cout << "ctor: CString(const CString& c)" << endl;
// }
// CString(const CString& c) = delete;
};
int main()
{
CString cs1; // 等价于CString cs1(), 调用默认构造函数
CString cs2("hello, world."); // 直接初始化
CString cs3 = "hello, world."; // 复制初始化
CString cs4(cs3); // 复制构造函数
CString cs5 = cs3; // 复制构造函数, 两者等价
return 0;
}
当启用复制构造函数时(第 19-23 行),代码能够编译运行,输出如下:
ctor: CString()
ctor: CString(const char* pc)
ctor: CString(const char* pc)
ctor: CString(const CString& c)
ctor: CString(const CString& c)
看起来,cs2 和 cs3 都调用了“使用C风格字符串初始化”的构造函数,直接初始化和复制初始化没有区别?
其实并非如此,当禁用复制构造函数时(第 25 行),再编译运行会得到如下错误:
error: use of deleted function 'CString::CString(const CString&)'
CString cs3 = "hello, world.";
error: use of deleted function 'CString::CString(const CString&)'
CString cs4(cs3);
error: use of deleted function 'CString::CString(const CString&)'
CString cs5 = cs3;
cs3 - cs5 的初始化都失败了,3 个报错都是因为禁用了复制构造函数。cs4 和 cs5 用相同类型的对象进行初始化,调用复制构造函数,血脉纯正。而 cs3 的却有些不一样,它初始化使用的是一个 C 风格字符串,但从第 35 行上来看,非常像一种隐式类型转换:C 风格字符串对象转换为自定义类型 CString!
究其原因便是:cs3 初始化首先调用 CString(const char* pc)
构造一个临时对象 CString temp
,然后编译器调用复制构造函数将 temp 复制到 cs3。 因此禁用复制构造函数后,cs3 初始化失败。
那既然 cs3 的初始化过程中编译器帮忙偷偷调用了复制构造函数,那为什么没有对应的输出呢?我也不知道,tnnd= =。
因此,当形参类型与自定义类型不相同时,自定义类型的复制初始化往往比直接初始化多一个临时对象的构造和析构成本,至于成本有多大,就要看该自定义类型的大小、复杂性了。
同时也可以发现一个问题:构造函数可以起到一种隐式类型转换的功能。
但其实关于自定义类型向其他类型转换,是有官方语法的,与本文主题不相关,向了解的话搜索关键词“类型转换运算符”。
要点总结
- 单个参数的构造函数存在隐式类型转换的功能(严格来说,这样的构造函数可以有多个参数,但只有一个参数没有指定默认值)
- 直接初始化通常比复制初始化成本低。直接初始化根据参数调用最合适的构造函数直接构造出对象,而复制初始化在被复制对象类型与自定义类型不一致时,存在临时对象的构造和析构成本。