C ++构造函数很有趣 - 用自己的副本构造Foo

时间:2010-02-24 19:45:14

标签: c++ constructor

我有:

class Foo;

class Bar {
  Foo foo;
  Bar(): foo(foo) {};
}

Bar bar;

此时,是

bar.foo // <--- how is this initialized?

[这个问题来自一个错误的引用计数指针实现;我本可以发誓我确保每个指针都指向非空的东西;但我最终得到了一个指向NULL的指针。]

4 个答案:

答案 0 :(得分:12)

一旦输入构造函数的主体,

foo就完全初始化了(这是保证的一般情况;特别是在初始化列表中完成初始化之后。)

在您的情况下,您是从非构造对象进行复制构造。这导致未定义的行为,根据§12.7/ 1(谢谢你,gf):

  

对于非POD类类型的对象(第9节),在构造函数开始执行之前和析构函数完成执行之后,引用该对象的任何非静态成员或基类会导致未定义的行为。

事实上,它给出了这个例子:

struct W { int j; };
struct X : public virtual W { };
struct Y {
    int *p;
    X x;
    Y() : p(&x.j) // undefined, x is not yet constructed
    { }
};

注意,根据§1.4/ 1,编译器需要对未定义的行为进行诊断。虽然我认为我们都同意它会很好,但它根本不是编译器实现者需要担心的事情。


查尔斯指出了各种各样的漏洞。如果Bar具有静态存储且Foo是POD类型,则在此代码运行时初始化。静态存储变量在其他初始化运行之前进行零初始化。

这意味着无论Foo是什么,只要它不需要运行构造函数来进行初始化(即,是POD),它的成员将被零初始化。基本上,你将复制零初始化对象。

一般而言,应避免使用此类代码。 :)

答案 1 :(得分:5)

Bar(): foo(foo) {};

这将调用foo的复制构造函数,从而从非初始化对象复制构造。这将导致未定义的行为,除非您已实现处理该特定情况的复制构造函数,例如:

class Foo
{
    public:
        Foo()
        {
            std::cout << "Foo()";
        }

        Foo(const Foo& from)
        {
            if(this == &from) std::cout << "special case";
            else std::cout << "other case"; 
        }
};

但是这种特殊情况通常用于其他目的,比如廉价的字符串副本(当使用字符串类时)。所以不要试图利用这种特殊情况;)

答案 2 :(得分:3)

稍微扩展的代码版本似乎表明不,foo永远不会被初始化;你似乎有未定义的行为。在此示例中,永远不会打印"Foo()",表示没有构造Foo的实例:

#include <iostream>

class Foo {
public:
    Foo() { std::cerr << "Foo()"; }
};

class Bar {
public:
    Foo foo;
    Bar(): foo(foo) {};
};

int main() {
    Bar bar;
}

答案 3 :(得分:0)

不是Foo使用默认的内在构造函数,初始化列表会自动调用默认构造函数来初始化对象吗?