我倾向于使用我的构造函数的成员初始化列表…但我早就忘了这背后的原因了……
在构造函数中使用成员初始化列表吗?如果有,为什么?如果不是,为什么不是?
我倾向于使用我的构造函数的成员初始化列表…但我早就忘了这背后的原因了……
在构造函数中使用成员初始化列表吗?如果有,为什么?如果不是,为什么不是?
当前回答
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
编译器遵循以下步骤创建MyClass类型的对象:
类型的构造函数首先为“a”调用。 “Type”的赋值操作符在MyClass()构造函数体中被调用以赋值。
variable = a;
最后,Type的析构函数被调用为a,因为它超出了作用域。
现在考虑MyClass()构造函数带有初始化列表的相同代码:
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
使用初始化器列表,编译器执行以下步骤:
调用Type类的复制构造函数初始化:variable(a)。初始化器列表中的参数用于直接复制构造“variable”。 “Type”的析构函数被调用为“a”,因为它超出了作用域。
其他回答
对于POD的成员来说,这没有什么区别,只是风格的问题。对于属于类的类成员,则避免不必要地调用默认构造函数。考虑:
class A
{
public:
A() { x = 0; }
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B()
{
a.x = 3;
}
private:
A a;
};
在这种情况下,B的构造函数将调用A的默认构造函数,然后将A .x初始化为3。更好的方法是让B的构造函数直接在初始化列表中调用A的构造函数:
B()
: a(3)
{
}
这将只调用A的A(int)构造函数,而不调用其默认构造函数。在这个例子中,差异可以忽略不计,但是想象一下A的默认构造函数做了更多的工作,比如分配内存或打开文件。你不会想做不必要的事情。
此外,如果一个类没有默认构造函数,或者你有一个const成员变量,你必须使用初始化列表:
class A
{
public:
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list;
{ // it is an error not to do so
}
private:
A a;
const int y;
};
除了上面提到的性能原因,如果你的类存储了作为构造函数参数传递的对象的引用,或者你的类有const变量,那么你除了使用初始化列表之外没有任何选择。
除了性能问题,还有一个非常重要的问题,我称之为代码可维护性和可扩展性。
如果T是POD,并且你开始倾向于初始化列表,那么如果有一次T将改变为非POD类型,你不需要改变初始化周围的任何东西,以避免不必要的构造函数调用,因为它已经优化了。
如果类型T确实有默认构造函数和一个或多个用户定义构造函数,并且有一次您决定删除或隐藏默认构造函数,那么如果使用了初始化列表,则不需要更新用户定义构造函数的代码,因为它们已经正确实现了。
与const成员或引用成员一样,假设最初的T定义如下:
struct T
{
T() { a = 5; }
private:
int a;
};
接下来,你决定将a限定为const,如果你从一开始就使用初始化列表,那么这是一个单行更改,但有了上面定义的T,它还需要挖掘构造函数定义以删除赋值:
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
如果代码不是由“代码猴子”编写的,而是由工程师根据对所做事情的深入考虑来做决定,那么维护就会更容易,更不容易出错,这不是什么秘密。
在运行构造函数体之前,调用父类的所有构造函数,然后调用其字段的所有构造函数。默认情况下,调用无参数构造函数。初始化列表允许您选择调用哪个构造函数以及该构造函数接收哪些参数。
如果有引用或const字段,或者使用的类中有一个没有默认构造函数,则必须使用初始化列表。
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
编译器遵循以下步骤创建MyClass类型的对象:
类型的构造函数首先为“a”调用。 “Type”的赋值操作符在MyClass()构造函数体中被调用以赋值。
variable = a;
最后,Type的析构函数被调用为a,因为它超出了作用域。
现在考虑MyClass()构造函数带有初始化列表的相同代码:
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
使用初始化器列表,编译器执行以下步骤:
调用Type类的复制构造函数初始化:variable(a)。初始化器列表中的参数用于直接复制构造“variable”。 “Type”的析构函数被调用为“a”,因为它超出了作用域。