假设我有这样一个函数:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
在每一组中,这些陈述是否相同?或者在某些初始化中是否存在额外的(可能是可优化的)副本?
我见过有人两种说法都说。请引用原文作为证明。也请添加其他案例。
值得注意的是:
[12.2/1]类类型的临时对象可以在不同的上下文中创建:…在一些初始化中(8.5)。
例如,用于复制初始化。
[12.8/15]当满足某些条件时,允许实现省略类对象的复制构造…
换句话说,一个好的编译器不会在可以避免的情况下创建副本进行复制初始化;相反,它将直接调用构造函数——即,就像直接初始化一样。
换句话说,复制初始化就像在大多数<opinion>的情况下直接初始化,其中已经编写了可理解的代码。由于直接初始化可能会导致任意(因此可能是未知的)转换,我更喜欢在可能的情况下总是使用复制初始化。(额外的是,它实际上看起来像初始化。)</opinion>
技术血淋淋的景象:
[12.2/1接续以上]即使避免了临时对象的创建(12.8),也必须遵守所有的语义限制,就像创建了临时对象一样。
很高兴我不是在写c++编译器。
赋值不同于初始化。
以下两行代码都进行初始化。一个构造函数调用完成:
A a1 = A_factory_func(); // calls copy constructor
A a1(A_factory_func()); // calls copy constructor
但它不等于:
A a1; // calls default constructor
a1 = A_factory_func(); // (assignment) calls operator =
我现在还没有一篇文章来证明这一点,但这很容易实验:
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
A(const A& x) {
cout << "copy constructor" << endl;
}
const A& operator = (const A& x) {
cout << "operator =" << endl;
return *this;
}
};
int main() {
A a; // default constructor
A b(a); // copy constructor
A c = a; // copy constructor
c = b; // operator =
return 0;
}
很多情况都取决于对象的实现,所以很难给你一个具体的答案。
考虑一下这个案例
A a = 5;
A a(5);
在这种情况下,假设一个适当的赋值操作符和初始化构造函数接受一个单一的整数参数,我如何实现这些方法影响每一行的行为。然而,通常的做法是,其中一个在实现中调用另一个,以消除重复的代码(尽管在这样简单的情况下,没有真正的目的)。
编辑:正如在其他响应中提到的,第一行实际上将调用复制构造函数。将与赋值操作符相关的注释视为与独立赋值相关的行为。
也就是说,编译器如何优化代码会有它自己的影响。如果初始化构造函数调用“=”运算符——如果编译器不做任何优化,则顶部行将执行两次跳转,而底部行则执行一次跳转。
现在,对于最常见的情况,编译器将优化这些情况并消除这种低效率。所以实际上你所描述的所有不同情况都是一样的。如果您想确切地了解正在执行的操作,可以查看编译器的目标代码或汇编输出。
值得注意的是:
[12.2/1]类类型的临时对象可以在不同的上下文中创建:…在一些初始化中(8.5)。
例如,用于复制初始化。
[12.8/15]当满足某些条件时,允许实现省略类对象的复制构造…
换句话说,一个好的编译器不会在可以避免的情况下创建副本进行复制初始化;相反,它将直接调用构造函数——即,就像直接初始化一样。
换句话说,复制初始化就像在大多数<opinion>的情况下直接初始化,其中已经编写了可理解的代码。由于直接初始化可能会导致任意(因此可能是未知的)转换,我更喜欢在可能的情况下总是使用复制初始化。(额外的是,它实际上看起来像初始化。)</opinion>
技术血淋淋的景象:
[12.2/1接续以上]即使避免了临时对象的创建(12.8),也必须遵守所有的语义限制,就像创建了临时对象一样。
很高兴我不是在写c++编译器。