如果用户定义的构造函数省略了数据成员的初始化,会发生什么?

时间:2013-12-29 07:58:14

标签: c++ c++11

假设:

class Foo
{
public: 
    Foo();
    Foo(typeA a);
private:
    typeB b;
    typeC c;
};

Foo::Foo(typeA a)
{
    //do something with a, but nothing with b nor c.
}

int main()
{
    typeA a;
    Foo foo(a);
    return 0;
}

这里会发生什么? foo.bfoo.c默认是否已初始化? 以这种方式定义构造函数是一个好习惯吗?

3 个答案:

答案 0 :(得分:9)

  

foo.b和foo.c是否默认初始化?

是的,数据成员将隐式默认初始化。但默认初始化的作用取决于要初始化的对象的类型。对于用户定义的类型,这意味着将调用它们的默认构造函数。对于内置插件或POD,这意味着将执行无初始化。这取决于typeAtypeB是:

如果它们是用户定义的类型,则将调用它们的默认构造函数。如果它们是内置函数或PODS,则不会进行初始化。

  

以这种方式定义构造函数是一个好习惯吗?

假设您拥有用户定义类型的数据成员,那更多的是约定和编码标准。我已经看到它有两种方式:一方面,如果值初始化和隐式默认初始化做同样的事情(如用户定义的类型),为什么显式值初始化数据成员?有人可能会说你不知道自己在做什么。另一方面,为什么不明确一切呢?如果将数据类型从用户定义的类型更改为内置类型并忘记将其添加到构造函数的初始化列表中,该怎么办?我倾向于参与论证的“我知道我在做什么”,但这纯粹是个人的。

如果您的数据成员是内置函数或POD,那么它实际上归结为您自己的代码的要求:您是否要对成员进行零初始化?通常你这样做,但是这个初始化不是免费的,而可能重要的是某些情况。这就是这些类型在C ++中默认初始化为零的原因。所以,再一次,没有明确的答案,但在大多数实际代码中我已经处理过你希望成员被零初始化。

答案 1 :(得分:2)

嗯......这并不总是一个好习惯,因为这个效果会在以后使用对象时变得明显。

构造函数的要点是“构造”某些东西,以便可以安全地使用它,因此必须完成它。

保持bc不变的问题将表现出不同的行为,具体取决于typeBtypeC是用户类型或内置类型。

Buitin类型要求构造是显式的(否则将包含“随机”值)。用户类型总是默认构造的(注意问题是递归的:如果用户类型包含内置类型并且没有初始化它,它的'默认构造将使内置类型未初始化)。

因此,如果typeBtypeC是通用的(模板参数或从模板参数计算)并且可以内置类型,则初始化是必须的.​​..除非您承认结果对象是“有效的“即使某些成员的随机值(这意味着什么,取决于对象的语义)

如果typeBtypeC是已知的用户类型并且从他们自己的默认ctors完全初始化,那么你可以避免显式调用(因为它将隐式完成)

注意-in C ++ 11-你可以在成员声明中指定成员的初始化,而不是构造:

class Foo
{
public: 
    Foo();
    Foo(typeA a);
private:
    typeB b = 5; //<---- note this!
    typeC c = 7; //<---- note this!
};

这将允许所有构造函数找到这些值,除非由特定的初始化列表进行不同的初始化。在实践中是一种“默认默认值”。

如果类型是通用的,通过提供空的初始化程序来显式初始化它们并不罕见:

template<class T>
class wrapper
{
    T m = {} //<---- note the empty {}
public:
    wrapper() {}
    wrapper(T&& t) :m(std::forward<T>(t)) {}
    wrapper(const wrapper& s) :m(s.m) {}
    wrapper(wrapper&& s) { swap(s); }
    wrapepr& operator=(wrapper s) { swap(s); return *this; }
    ~wrapper() { /* cleanup m */ }
    void swap(wrapper& s) { std::swap(m,s.m); }
};

答案 2 :(得分:0)

完全取决于究竟是什么

type B & type C.

如果它们是原始/内置类型,则它们不会被初始化。否则,对于派生类型,它们将被默认初始化。