为什么未构造的对象可以在派生初始化列表

时间:2015-07-08 18:29:37

标签: c++ undefined-behavior initialization-list

我有一个类层次结构,在派生类中,声明了Dog类的对象。然后,该对象被提供给初始化列表中的基类。

由于在调用基类构造函数之前未构造Dog类对象,因此该对象不应该有任何用处。但是,我看到,该对象可用于以下代码:

#include <iostream>
using namespace std;

class Dog {
    public:
        Dog() {cout << "\n In Dog";}
        void dfoo(){cout << "\n In dfoo";};
};

class Foo {
    public:
        Foo(Dog d){cout << "\n In Foo"; 
        d.dfoo();
    }
};

class Bar : public Foo {
    Dog d;
    public:
        Bar() : Foo(d) {
            cout << "\n In Bar";
        }
};

int main() {
    cout << "\nHello";
    Bar b;

}

输出是:

Hello In Foo In dfoo In Dog In Bar 输出显示甚至在构造Dog对象之前调用了dfoo()。这是怎么回事?不应该&#39; d&#39;是垃圾,因为在调用Base构造函数之前没有初始化它?

4 个答案:

答案 0 :(得分:2)

实际上,你的对象d包含垃圾。

当你传递d(按值)时,你隐式调用Dog的复制构造函数,并通过在Dog类型的垃圾对象上执行复制构造函数来创建一个类型为Dog的新对象。

所以你的问题是:为什么Dog的隐式复制构造函数不关心源对象从未构造过。

由于该对象甚至没有任何内容,因此编译器为了使其复制构造函数关心其源代码而浪费了大量精力。

我不确切知道有多少是未定义的行为(恰好因为实现是理智的)而不是你可能允许用未构造的对象做的事情。

考虑这个版本的狗:

class Dog {
    public:
        Dog() {cout << "\n In Dog";}
        Dog(Dog const& source) { cout << "\n Trying to copy a Dog\n"; source.dfoo();}
        virtual void dfoo() const {cout << "\n In dfoo";};
};

现在你对它的使用会以你预期的方式崩溃(因为复制构造函数的源代码从未构造过。)

您对Dog的复制构造函数的调用隐藏在将值传递给Foo的构造函数的操作中。注意它发生在实际到达Foo的构造函数之前。 Dog的复制结构(及其所有好处和/或问题)在有或没有Dog的复制构造函数的明确定义的情况下发生。如果您没有定义它,编译器会为您定义它。

答案 1 :(得分:1)

理论上,您的代码会受到未定义的行为的影响。

您正在将未初始化的d传递给Foo::Foo()。您没有看到任何问题,因为Dog没有任何成员变量,Dog::dfoo()不依赖于任何成员变量的值。

如果您将成员变量添加到Dog并在Dog::dfoo()中使用该成员变量,您可能会看到奇怪的输出:

class Dog {
    int v1;
    public:
        Dog() {cout << "\n In Dog";}
        void dfoo(){cout << "\n In dfoo, v1: " << v1 << endl;};
};

答案 2 :(得分:0)

如果您在clang中编译代码,您将收到警告,但您将获得相同的输出。

clang++ -std=c++11  -Wall  -pedantic -pthread main.cpp && ./a.out
  

main.cpp:21:21:警告:字段“d”在此处使用时未初始化   [-Wuninitialized] Bar():Foo(d){

那发生了什么?

您正在将未初始化的Dog对象作为参数传递给Foo的构造函数。由于您的构造函数接受Dog参数作为值,它将尝试调用类Dog的复制构造函数来创建Dog的对象,即使它未初始化。并且系统成功使用Foo构造函数内的复制构造函数创建了一个本地Dog对象,并调用了函数dfoo()。

答案 3 :(得分:0)

垃圾。

不确定您对此有何明显的症状。例如,Dog没有数据成员。