假设我们有一个自定义字符串类,如下所示
using namespace std;
class CustomStr {
public:
const char* s;
// converting constructor
CustomStr(const char* s) : s (s) {
cout << "Constructor called" << endl;
}
// Copy constructor
CustomStr(const CustomStr& cs) : s (cs.s) {
cout << "Copy Constructor called" << endl;
}
};
让我们考虑以下代码:
int main() {
CustomStr cs("Some char pointer");
CustomStr cs_copy = cs;
return 0;
}
在上述情况下,我们期望CustomStr cs_copy = cs
首先调用转换构造函数,然后再调用复制构造函数,并按预期获得相应的输出
$ ./a.out
Constructor called
Copy Constructor called
现在,考虑以下代码
int main() {
CustomStr cs_copy = CustomStr("Some char pointer");
return 0;
}
在这种情况下,我也认为应该进行2个构造函数调用-转换CustomStr("Some char pointer")
的构造函数调用和复制CustomStr cs_copy = CustomStr("Some char pointer")
的构造函数调用。但是,输出仅显示正在转换的构造函数。
我无法解释这是怎么发生的。我希望它对编译器进行优化是一项艰巨的任务-但我想了解在什么情况下可以进行此优化。
答案 0 :(得分:3)
引用cppreference:
如果初始值设定项是类型与T相同的prvalue表达式(忽略cv限定),则将初始值设定项表达式本身(而不是从其实例化的初始值)用于初始化目标对象
CustomStr("Some char pointer")
是与cs_copy
类型相同的prvalue。因此,没有临时的副本。 cs_copy
可以直接从该表达式直接初始化。
在C ++ 17之前,就抽象机而言曾经是一个临时的,但是该标准不需要调用复制构造函数,因此与C ++ 17相同的行为被允许作为优化。有关更多详细信息,请参见cppreference:copy elision。
答案 1 :(得分:2)
自C ++ 17起,T x = T(A);
的定义与以下内容完全相同:T x(A);
。 (其中A
是任何表达式列表,可能是空的,并且没有MVP)。
在C ++ 17之前,编译器可以选择是否将代码作为T x(A);
处理,还是选择构造一个临时文件,然后从该临时文件中复制/移动构造文件x
。