在构造函数中初始化字段 - 初始化列表 vs 构造函数体

93

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

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

并且

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

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

对于第一个示例不太熟悉,如果我犯了错误,请原谅。

6个回答

102

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

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

等同于

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

因为它们将在构造函数体开始执行之前初始化,因此基本上是做了两倍的工作。这也意味着,如果这些成员的类型没有默认构造函数,则您的代码将无法编译。


我一直在使用自定义数据类型,即使我没有默认构造函数,并且使用我第二个示例,在gcc或VS2008/2010上从未遇到任何编译器错误。 - gardian06
我的意思是拥有一个参数化构造函数,但没有默认值,然后使用我的第二个例子来操作为自定义对象的构造函数提供参数。没有错误。当创建抽象数据类型时,不拥有默认构造函数并不罕见。 - gardian06
1
@gardian06,那听起来不对劲。你有一个小的、可编译的示例吗? - juanchopanza
@gardian06,你的编译器有问题 - http://ideone.com/nerKS - Luchian Grigore
2
做两倍的工作,其实并不是。只是更多的工作,而不是两倍的工作。 - Alok Save
显示剩余4条评论

25

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

当你仅在构造函数体内初始化成员时,如果编译器可以生成默认成员初始化语句,则会为你生成一个,因此你最终会对其进行双重初始化。这在某些情况下可能不是大问题,但如果构建对象很昂贵,则可能严重影响性能。

更新

然而,没有(明确定义或生成的)默认构造函数的用户定义类型无法以这种方式初始化,因此会产生编译错误。对于const和引用字段也是如此 - 这些只能在成员初始化列表中显式初始化。


2
然后编译器就不高兴了。此外,初始化列表是初始化对象的const字段的唯一方法。 - Griwes
@gardian06,是的,如果有默认构造函数。否则对于基本类型来说,在技术上没有区别,但对于用户定义的类型会产生编译器错误,因为编译器无法初始化这些字段。对于“const”和引用字段也是如此-这些只能在成员初始值列表中初始化。 - Péter Török
那么将这两种方法结合起来使用是可能的吗?对于常量和本地类型,可以使用初始化列表,然后在函数体中进行更深入的工作吗?我只见过它们分别使用。 - gardian06
我已经使用用户定义类型有一段时间了,无论是有默认构造函数还是没有,默认值列表都没有问题。 - gardian06
我的意思是拥有一个参数化构造函数,但没有默认值,然后使用我的第二个示例来为自定义对象的构造函数提供参数。没有错误。 - gardian06
显示剩余3条评论

18

除了Péter Török的回答外,需要补充的是初始化列表是初始化对象的常量成员的唯一方法,例如:

class foo
{
public:

    foo(int value)
        : myConstValue(value)
    {};

    foo()
    {
        myConstValue = 0; // <=== Error! myConstValue is const (RValue), you can't assign!
    };

private:
    const int myConstValue;
}

这是因为const变量不能被更改,而且它们在创建时必须给定一个初始值。引用类型也是同样的道理。 - Galaxy

6
在你的示例代码中,第一个是在构造函数初始化列表中进行初始化,第二个是在构造函数体内进行赋值。
构造函数初始化列表是执行所有成员初始化的最佳方式,因为它可以提高性能。
class A
{
string name;
public:
A(string myname):name(myname) {}
}

在上述情况下,编译器不会创建临时对象来进行初始化。然而在以下情况下:
A::A()
{
    name = myname;
}

创建了一个单独的临时对象,然后将该临时对象传递给 string 的赋值运算符以分配给 name。然后销毁了临时对象,这不太高效。

注意:引用或常量成员必须在构造函数初始化列表中进行初始化,不能在构造函数体内进行“赋值”。


2
除了其他答案外,我想提到构造函数初始化仅用于初始化成员变量。
class Demo
{
    int a;
    int b;
public:
    Demo(int a,int b):a(a),b(b)
    {

    }

};

如果我们在构造函数中初始化a和b,则会导致自我赋值。

1
不错,如果你想在构造函数的内部进行赋值,你也可以使用 this->a = a; 来区分成员变量和参数。 - Galaxy

1
首先是初始化,使用初始化列表;其次是默认构造然后赋值。第一个方法至少和第二个一样快,并且更可取。

使用第二个会不会因为没有要初始化的类型的默认构造函数而产生问题? - gardian06
是的,除非它是内置类型,比如int之类的,否则会得到编译器错误,如果我记得正确的话。 - DumbCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接