Consider the following code:
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) {}
};
Now trying to compile this code with 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 does not print any warnings, not even with -Wall -Wextra
and -pedantic
.
It's undefined behavior. According to [class.base.init]:
In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes ...
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
b
won't have been initialized by the time that the A
base class is initialized. The assignment b = 0
is itself undefined behavior for the same reason - b
hadn't been initialized yet when that is called. Its default constructor would still be called after A
's constructor.
If you want ensure that b
is initialized first, the typical approach is the 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.
{ };
};
In either case, calling a member function before base classes are initialized invokes undefined behavior. §12.6.2/16:
Member functions (including virtual member functions, 10.3) can be called for an object under construction. Similarly, an object under construction can be the operand of the
typeid
operator (5.2.8) or of adynamic_cast
(5.2.7). However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined. [ Example: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 };
However, the access of and assignment to b
itself is fine, since it has vacuous initialization and its lifetime starts as soon as storage is acquired for it (which happened long before the constructor call started). Hence
class B : public A {
private:
int b;
public:
B() : A(b=0) {}
};
is well-defined.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With