构造函数应该初始化其所有成员对象 如果可能,初始化列表。它比建立它更有效 构造函数通过构造函数体内的赋值。
有人可以解释一下,为什么在示例的帮助下使用初始化列表更有效?
答案 0 :(得分:14)
考虑这个程序:
#include <iostream>
struct A {
A() { std::cout << "A::A()\n"; }
A(int) { std::cout << "A::(int)\n"; }
void operator=(const A&) { std::cout << "A::operator=(const A&)\n"; }
};
struct C1 {
A a;
C1(int i) {
a = i;
}
};
struct C2 {
A a;
C2(int i) : a(i) {}
};
int main() {
std::cout << "How expesive is it to create a C1?\n";
{ C1 c1(7); }
std::cout << "How expensive is it to create a C2?\n";
{ C2 c2(7); }
}
在我的系统(Ubuntu 11.10,g ++ 4.6.1)上,程序产生了这个输出:
How expesive is it to create a C1?
A::A()
A::(int)
A::operator=(const A&)
How expensive is it to create a C2?
A::(int)
现在,考虑一下为什么这样做。在第一种情况下,C1::C1(int)
,a
必须默认构造,然后才能调用C1
的构造函数。然后必须通过operator=
分配。在我的简单示例中,没有int
赋值运算符可用,因此我们必须从int构造A
。因此,不使用初始化程序的成本是:一个默认构造函数,一个int
构造函数和一个赋值运算符。
在第二种情况下,C2::C2(int)
,仅调用int
构造函数。无论默认A
构造函数的成本是多少,显然C2:C2(int)
的成本不会高于C1::C1(int)
的成本。
<小时/> 或者,考虑这个替代方案。假设我们将以下成员添加到
A
:
void operator=(int) { std::cout << "A::operator=(int)\n"; }
然后输出将显示为:
How expesive is it to create a C1?
A::A()
A::operator=(int)
How expensive is it to create a C2?
A::(int)
现在不可能一般地说哪种形式更有效率。在您的特定类中,默认构造函数的成本加上赋值的成本比非默认构造函数更昂贵吗?如果是这样,则初始化列表更有效。否则就不是。
我写过的大多数类都会在init列表中更有效地初始化。但是,这是一个经验法则,并不一定适用于所有可能的情况。
答案 1 :(得分:9)
好吧,否则你调用默认构造函数,然后执行赋值。这是一个更长的步骤,根据初始化的性质可能会变得非常低效。
答案 2 :(得分:5)
因为它直接初始化,而不是默认初始化然后分配。对于POD来说,性能方面可能无关紧要,但如果类型构造函数正在进行繁重的工作,那么它将会起作用。
此外,在某些情况下,必须使用init列表,因此您应该始终保持一致性。
答案 3 :(得分:2)
来自C++FAQ:
考虑以下使用初始化列表初始化成员对象x_的构造函数:Fred :: Fred():x_(whatever){}。这样做的最常见好处是提高了性能。例如,如果表达式与成员变量x_的类型相同,那么任何表达式的结果直接在x_中构造 - 编译器不会创建该对象的单独副本。即使类型不同,编译器通常也能够使用初始化列表比使用赋值做得更好。
构建构造函数的另一种(低效)方法是通过赋值,例如:Fred :: Fred(){x_ = whatever; }。在这种情况下,表达式会导致创建单独的临时对象,并且此临时对象将传递到x_对象的赋值运算符。然后该临时对象在;处被破坏。那效率很低。
好像这还不够糟糕,在构造函数中使用赋值时还有另一个低效率的来源:成员对象将由其默认构造函数完全构造,例如,这可能会分配一些默认的内存量或打开一些默认文件。如果无论表达式和/或赋值运算符导致对象关闭该文件和/或释放该内存(例如,如果默认构造函数没有分配足够大的内存池或者它是否打开),所有这些工作都可能是徒劳的。错误的文件)。
答案 4 :(得分:0)
防止双重初始化。
class B
{
//whatever
};
class A
{
B _b;
public:
A(B& b)
};
现在有两种情况:
//only initializes _b once via a copy constructor
A::A(B& b) : _b(b)
{
}
//initializes _b once before the constructor body, and then copies the new value
A::A(B& b)
{
//_b is already initialized here
//....
//one extra instruction:
_b = b;
}
答案 5 :(得分:0)
就POD类型而言,初始化和赋值应该是等效的,因为如果没有明确地执行初始化它们就会保持未初始化,因此唯一的操作仍然是赋值。
具有默认构造函数和赋值运算符的类的情况有所不同:不是直接在正确的状态下创建对象,首先必须调用默认构造函数,然后调用赋值运算符(在构造函数体内)。这肯定比从开头直接使用正确的构造函数初始化对象效率更低(两步而不是一步,第一步 - 默认构造 - 通常完全浪费)。
直接初始化还产生的优势是,如果执行到达构造函数的主体,则所有各个字段都已处于“正确”状态。
答案 6 :(得分:0)
假设您的类中有一个std :: string类型的数据成员。执行此类的构造函数时,将自动调用默认构造函数字符串类,因为在构造函数的主体之前初始化了对象。
如果要在构造函数体内分配字符串,则会创建一个临时对象并将其赋予字符串的赋值运算符。临时对象将在赋值语句结束时销毁。如果在初始化列表中执行此操作,则不会创建临时对象。
class Person
{
public:
Person(string s);
~Person();
private:
string name;
};
// case 1
Person::Person(string s)
{
name = s; // creates a temporary object
}
// case 2
Person::Person(string s):name(s) {} // whereas this will not create a temporary object.
答案 7 :(得分:0)
因为如果您使用的是inizializer列表,那么您调用的是该对象的构造函数副本。
如果你在构造函数体内初始化对象,那么你正在做一个赋值。
例如: 在这里我调用int的复制构造函数。
myClass::myClass( int x ) : member(x) {}
在这里我调用operator =(const int&amp;)。对齐
myClass::myClass( int x ) : member(x) {}
通常,asignment会执行更多操作,然后是简单的副本。 你必须考虑临时对象!