初始化构造函数中的字段-初始化器列表与构造函数正文

Initializing fields in constructor - initializer list vs constructor body

本文关键字:初始化 构造函数 列表 正文 字段      更新时间:2023-10-16

我已经在C++中工作了一段时间,但我不确定这两个选项之间的区别:

public : Thing(int _foo, int _bar): member1(_foo), member2(_bar){}

public : Thing(int _foo, int _bar){
    member1 = _foo;
    member2 = _bar;
}

我有一种感觉,他们做同样的事情,但这两种语法之间有实际的区别吗。其中一个比另一个更安全吗?它们对默认参数的处理方式不同吗。

我不太习惯第一个例子,所以如果我犯了错误,我道歉。

如果member1member2是非POD(即非plainOldData)类型,则它们不相同:

public : Thing(int _foo, int _bar){
    member1 = _foo;
    member2 = _bar;
}

相当于

public : Thing(int _foo, int _bar) : member1(), member2(){
    member1 = _foo;
    member2 = _bar;
}

因为它们将在构造函数主体开始执行之前初始化,所以基本上完成了两次工作。这也意味着,如果这些成员的类型没有默认构造函数,那么您的代码将不会编译。

第一种是推荐的最佳实践,因为它更惯用,并且避免为具有默认构造函数的类型(即非基元类型)重新初始化字段。

当你只初始化构造函数体内的一个成员时,编译器会为你生成一个默认的成员初始化语句(如果可以的话),所以你最终会对它进行双重初始化。在某些情况下,这可能不是什么大不了的事,但如果构建对象的成本很高,可能会带来严重的性能开销。

更新

但是,没有(n显式定义或生成)默认构造函数的用户定义类型无法以这种方式初始化,因此会产生编译器错误。常量字段和引用字段也是如此——它们只能在成员初始值设定项列表中显式初始化。

唯一需要添加到péter Török答案的是Initializer List是初始化对象常量成员的唯一方法,即:

class foo
{
public:
    foo(int value)
        : myConstValue(value)
    {};
    foo()
    {
        myConstValue = 0; // <=== Error! myConstValue is const (RValue), you can't assign!
    };
private:
    const int myConstValue;
}

在您的示例代码中,第一个是构造函数初始化,第二个是构造函数内部的赋值。

构造函数初始化列表是执行所有成员初始化的最佳方式,因为它可以提高性能。

class A
{
string name;
public:
A(string myname):name(myname) {}
}

在上述情况下,编译器不会创建临时对象来进行初始化。但是,在以下情况下:

A::A()
{
    name = myname;
}

将创建一个单独的临时对象,并将此临时对象传递给string的赋值运算符,以便将其赋值给name。然后临时对象被销毁,这不是很有效。

注意:必须在构造函数初始化列表中初始化引用或常量成员。它们不能在构造函数的主体中"赋值"。

除了其他答案之外,我想提到构造函数初始化仅用于初始化成员变量。

class Demo
{
    int a;
    int b;
public:
    Demo(int a,int b):a(a),b(b)
    {
    }
};

如果我们在构造函数中初始化a和b,它将是自赋值的。

第一个是初始化,使用初始化器列表,第二个是默认构造,然后是赋值。第一个至少和第二个一样快,并且比第二个更好。