C ++ class member initialization for class members (POD, POD and POD)
I am trying to understand how implicit initialization of class members works for {POD structs, POD classes and POD} s members. After reading a little bit, I expected them to be initialized to their default values, but the actual behavior seems to be different here -
#include <iostream>
struct S1
{
void* a;
int b;
};
struct S2
{
S2() { std::cout << "!"; }
void* a;
int b;
};
struct S3
{
S3() : a(), b() { std::cout << "!"; }
void* a;
int b;
};
class C1
{
public:
void* a;
int b;
};
class C2
{
public:
C2() { std::cout << "!"; }
void* a;
int b;
};
class C3
{
public:
C3() : a(), b() { std::cout << "!"; }
void* a;
int b;
};
template <typename T>
class FOO1
{
public:
T s;
int a;
};
template <typename T>
class FOO2
{
public:
FOO2() {}
T s;
int a;
};
template <typename T>
class FOO3
{
public:
FOO3() : s(), a() {}
T s;
int a;
};
//#define SKIP_S1C1
template <typename T>
void moo()
{
#ifndef SKIP_S1C1
T* f = new T();
T foo = *f;
std::cout << ":\ts = (" << foo.s.a << ", " << foo.s.b << ")\ta = " << foo.a << std::endl;
delete f;
#else
T foo;
std::cout << ":\ts = (" << foo.s.a << ", " << foo.s.b << ")\ta = " << foo.a << std::endl;
#endif
}
int main()
{
#ifndef SKIP_S1C1
moo<FOO1<S1> >();
#endif
moo<FOO1<S2> >();
moo<FOO1<S3> >();
#ifndef SKIP_S1C1
moo<FOO1<C1> >();
#endif
moo<FOO1<C2> >();
moo<FOO1<C3> >();
std::cout << std::endl;
#ifndef SKIP_S1C1
moo<FOO2<S1> >();
#endif
moo<FOO2<S2> >();
moo<FOO2<S3> >();
#ifndef SKIP_S1C1
moo<FOO2<C1> >();
#endif
moo<FOO2<C2> >();
moo<FOO2<C3> >();
std::cout << std::endl;
#ifndef SKIP_S1C1
moo<FOO3<S1> >();
#endif
moo<FOO3<S2> >();
moo<FOO3<S3> >();
#ifndef SKIP_S1C1
moo<FOO3<C1> >();
#endif
moo<FOO3<C2> >();
moo<FOO3<C3> >();
}
The obvious execution results are not enough to tell if the PODs were initialized to their default value of 0 or just contained noise. But here are some results:
Build and run on ubuntu with gcc 4.6.3 #define SKIP_S1C1
uncommented, I get
!: s = (0x7ffffe557770, 4196620) a = 1
!: s = (0, 0) a = 1
!: s = (0, 0) a = 1
!: s = (0, 0) a = 1
!: s = (0x1, 6299744) a = 6299744
!: s = (0, 0) a = 6299744
!: s = (0, 0) a = 6299744
!: s = (0, 0) a = 6299744
!: s = (0x1, 6299744) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
and with it commented, I get
: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
: s = (0, 0) a = 0
!: s = (0, 0) a = 0
!: s = (0, 0) a = 0
and with VS2013, with a comment,
: s = (00000000, 0) a = 0
!: s = (CDCDCDCD, -842150451) a = -842150451
!: s = (00000000, 0) a = -842150451
: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
: s = (CDCDCDCD, -842150451) a = -842150451
!: s = (CDCDCDCD, -842150451) a = -842150451
!: s = (00000000, 0) a = -842150451
: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
: s = (00000000, 0) a = 0
!: s = (CDCDCDCD, -842150451) a = 0
!: s = (00000000, 0) a = 0
: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
and no comment,
!: s = (CCCCCCCC, -858993460) a = -858993460
!: s = (00000000, 0) a = -858993460
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
!: s = (CCCCCCCC, -858993460) a = -858993460
!: s = (00000000, 0) a = -858993460
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
!: s = (CCCCCCCC, -858993460) a = 0
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
!: s = (00000000, 0) a = 0
I would really like to understand what I should expect and when is it UB when it comes to implicit initialization of {POS struct, POD and POD} members. Any help would be greatly appreciated ... :)
source to share
The constructors are tricky, the details are technical, but here's a general summary *:
There are three ways to initialize:
- Zero Initialize - The details are technical, but effectively sets all bits to zero. This bypasses the constructors
- Default Initialize - If it has a constructor, the default constructor is called. Otherwise, initialization fails. Reading from them is the UB you found.
- Initialize value - If it has a constructor, the default constructor is called. Otherwise, its bits are all (effectively) set to zero.
And they are called in many situations:
- static global variables - null initialized and then value initialized. (rather strange)
- locals - initialized by default.
-
new T;
- Initialization by default -
new T();
- Initialize value - member not in initialization list - Default Initialize
- member in the initialization list is the Initialize value.
See sections ยง8.5 and ยง12.6 in the C ++ 11 draft for details. They are long and boring.
Also note that the rules for C are technically surprisingly different, although the effects seem the same to me.
* My summary is not technically accurate, but accurate enough for most real-world codes. For example, arrays have special rules technically, but they are so intuitive that they are not worth mentioning.
** Yes, this is "Initialization" is "No initialization", which makes the other paragraphs about "if they were initialized" technically ambiguous, but apply common sense. It is not initialized.
source to share