请考虑以下代码:
class A {
private:
int a;
public:
A(int a) : a(a) { }
};
class B : public A {
private:
int b;
bool init() {
b = 0;
return true;
}
public:
// init() is a hack to initialize b before A()
// B() : b(0), A(b) {} yields -Wreorder
// B() : A((b = 0)) {} no warning (but this one doesn't work so well with non-pod (pointer) types)
B() : A(init() ? b : 0) {}
};
现在尝试使用clang编译此代码...
$ clang++ test.cpp -fsyntax-only
test.cpp:19:20: warning: field 'b' is uninitialized when used here [-Wuninitialized]
B() : A(init() ? b : 0) {}
^
1 warning generated.
GCC不打印任何警告,即使-Wall -Wextra
和-pedantic
也没有。
答案 0 :(得分:7)
这是未定义的行为。根据[class.base.init]:
到在非委托构造函数中,初始化按以下顺序进行:
- 首先,仅适用于派生程度最高的类(1.8)的构造函数,虚拟基类...
- 然后,直接基类按声明顺序初始化,因为它们出现在base-specifier-list中 (无论mem-initializers的顺序如何) - 然后,非静态数据成员按照在类定义中声明的顺序进行初始化 (再次不管mem-initializers的顺序如何)。
b
基类初始化时, A
将不会被初始化。由于同样的原因,赋值b = 0
本身是未定义的行为 - b
在调用时尚未初始化。它的默认构造函数仍将在A
的构造函数之后调用。
如果您想确保首先初始化b
,则典型方法是base-from-member idiom:
struct B_member {
int b;
B_member() : b(0) { }
};
class B : public B_member, public A
{
public:
B() : A(b) // B_member gets initialized first, which initializes b
// then A gets initialized using 'b'. No UB here.
{ };
};
答案 1 :(得分:6)
在任何一种情况下,在初始化基类之前调用成员函数会调用未定义的行为。 §12.6.2/ 16:
成员函数(包括虚拟成员函数,10.3)可以 要求建造一个物体。同样,一个对象下 构造可以是
typeid
运算符(5.2.8)或a的操作数dynamic_cast
(5.2.7)。 但是,如果这些操作是在a中执行的 在所有 mem-initializers 之前, ctor-initializer (或直接或间接从 ctor-initializer 中调用的函数) 基类已经完成,操作的结果是 undefined。 [例如:class A { public: A(int); }; class B : public A { int j; public: int f(); B() : A(f()), // undefined: calls member function // but base A not yet initialized j(f()) { } // well-defined: bases are all initialized };
然而,对b
本身的访问和赋值是很好的,因为它具有空的初始化,并且一旦为它获取存储(它在构造函数调用开始之前很久就发生),它的生命周期就开始了。因此
class B : public A {
private:
int b;
public:
B() : A(b=0) {}
};
定义明确。