假设我有这样一个函数:
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());
}
在每一组中,这些陈述是否相同?或者在某些初始化中是否存在额外的(可能是可优化的)副本?
我见过有人两种说法都说。请引用原文作为证明。也请添加其他案例。
赋值不同于初始化。
以下两行代码都进行初始化。一个构造函数调用完成:
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;
}
值得注意的是:
[12.2/1]类类型的临时对象可以在不同的上下文中创建:…在一些初始化中(8.5)。
例如,用于复制初始化。
[12.8/15]当满足某些条件时,允许实现省略类对象的复制构造…
换句话说,一个好的编译器不会在可以避免的情况下创建副本进行复制初始化;相反,它将直接调用构造函数——即,就像直接初始化一样。
换句话说,复制初始化就像在大多数<opinion>的情况下直接初始化,其中已经编写了可理解的代码。由于直接初始化可能会导致任意(因此可能是未知的)转换,我更喜欢在可能的情况下总是使用复制初始化。(额外的是,它实际上看起来像初始化。)</opinion>
技术血淋淋的景象:
[12.2/1接续以上]即使避免了临时对象的创建(12.8),也必须遵守所有的语义限制,就像创建了临时对象一样。
很高兴我不是在写c++编译器。
当初始化一个对象时,你可以看到显式和隐式构造函数类型的区别:
类:
class A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
};
class B
{
explicit B(int) { }
explicit B(int, int) { }
};
在main函数中:
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
}
默认情况下,构造函数是隐式的,所以你有两种方法来初始化它:
A a1 = 1; // this is copy initialization
A a2(2); // this is direct initialization
通过将结构定义为显式,你只有一种方式是直接的:
B b2(2); // this is direct initialization
B b5 = (B)1; // not problem if you either use of assign to initialize and cast it as static_cast
就这部分回答:
A c2 = A();c3 (());
由于大多数答案都是c++11之前的,我补充了c++11必须说的:
A simple-type-specifier (7.1.6.2) or typename-specifier (14.6)
followed by a parenthesized expression-list constructs a value of the
specified type given the expression list. If the expression list is a
single expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). If the type specified is a class type, the class
type shall be complete. If the expression list specifies more than a
single value, the type shall be a class with a suitably declared
constructor (8.5, 12.1), and the expression T(x1, x2, ...) is
equivalent in effect to the declaration T t(x1, x2, ...); for some
invented temporary variable t, with the result being the value of t as
a prvalue.
所以不管优化与否,它们在标准上是等价的。
请注意,这与前面提到的其他答案是一致的。只是为了正确起见,引用了标准的内容。
很多情况都取决于对象的实现,所以很难给你一个具体的答案。
考虑一下这个案例
A a = 5;
A a(5);
在这种情况下,假设一个适当的赋值操作符和初始化构造函数接受一个单一的整数参数,我如何实现这些方法影响每一行的行为。然而,通常的做法是,其中一个在实现中调用另一个,以消除重复的代码(尽管在这样简单的情况下,没有真正的目的)。
编辑:正如在其他响应中提到的,第一行实际上将调用复制构造函数。将与赋值操作符相关的注释视为与独立赋值相关的行为。
也就是说,编译器如何优化代码会有它自己的影响。如果初始化构造函数调用“=”运算符——如果编译器不做任何优化,则顶部行将执行两次跳转,而底部行则执行一次跳转。
现在,对于最常见的情况,编译器将优化这些情况并消除这种低效率。所以实际上你所描述的所有不同情况都是一样的。如果您想确切地了解正在执行的操作,可以查看编译器的目标代码或汇编输出。