These are my class definitions:
class Foo{
int _ent;
public:
void printEnt() const{cout << _ent << ' ';}
};
class Bar{
Foo _foo;
public:
void printEnt() const{_foo.printEnt();}
};
And this is my test code:
char* buf = new char[sizeof(Foo) + sizeof(Foo) + sizeof(Bar)];
fill(buf, buf + sizeof(Foo) + sizeof(Foo) + sizeof(Bar), 'J');
cout << ((int*)buf)[0] << ' ' << ((int*)buf)[1] << ' ' << ((int*)buf)[2] << endl;
Foo* first = new (buf) Foo;
Foo* second = new (buf + sizeof(Foo)) Foo();
Bar* third = new (buf + sizeof(Foo) * 2) Bar;
first->printEnt(); second->printEnt(); third->printEnt();
My output is:
1246382666 1246382666 1246382666
1246382666 0 1246382666
But if I add a public
default ctor to Foo
: Foo() : _ent(0) {}
My output becomes:
1246382666 1246382666 1246382666
0 0 0
Is this correct behavior? Should adding my own default ctor remove the possibility of default initialization?
I'm running this code on gcc 4.8.1 if it matters. The results should be dependable because I'm running in debug and asserting: assert(sizeof(Foo) == sizeof(int) && sizeof(Bar) == sizeof(int));
Once you provide a constructor for a type, it will always be
invoked, both for default initialization and for value
initialization. It's a fundamental principle of the language.
So once you define Foo::Foo()
, it will be called any time you
construct a Foo
; if there is a default constructor, it will be
invoked, even in the case of default initialization. So the
behavior you are seeing is correct.
EDIT:
Default initialization is explained §8.5/7, in particular:
To default-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T is called [...]
In your case, you'll probably also want to look at how the compiler generates a default constructor if none is provided, §12.1/4; in particular, the generated default constructor invokes the default constructor of any base classes or members.
Value initialization is in §8.5/8. It is basically default initialization preceded by zero initialization, so that default initialization that doesn't do anything still finds everything zero initialized.
More fundamentally, however: in this case, a very fundamental principle of C++ is involved, dating to long before the first standard: if you provide a constructor for an object, it will be used. Without doing all sorts of strange pointer casts, it is impossible to get an object without it being properly constructed. The standard describes how this occurs, and covers a lot of other special cases, but the basic principle has been there from the start (and any proposal which would cause it not to be respected in the standard is bound to fail).