std::vector initialization of class member - c++

I have read that std::vector always initializes it's objects with their default values say for an int it is 0. The same should be applicable even for classes where the default constructor is called. However the results shown by a test program are a bit different :-
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
int i;
A(){};
};
class B
{
public:
int i;
B() = default;
};
template <typename T>
void seev (const vector<T> &v)
{
for (auto &x:v)
{
cout << x.i << ' ';
}
cout << '\n';
}
int main()
{
vector<int> vi(5); // just to show that std::vector always default initializes values & hence int will be 0 here
vector<A> va(5);
vector<B> vb(5);
for (auto x: vi)
cout << x << ' ';
cout << '\n';
seev (va);
seev (vb);
return 0;
}
The output is :-
0 0 0 0 0
8854016 0 8847696 0 8854016
0 0 0 0 0
My question is why was the value of member i undefined for A & not for B ? What difference did the constructor signatures :-
A() {}
&
B() = default;
make ?

There is a difference between default initialization and value initialization. The difference is shown for B but not for A:
When a class has a non-= defaulted constructor, this constructor is called when either default or value initialization is used. Its responsibility is to initialize all members appropriately.
When a class either has no constructor or an = defaulted default constructor the initialization of the members depends on how the object is constructed: when initializing the object without parenthesis default initialization is done which leaves subobjects without a default constructor uninitialized. When initializing the object with parenthesis value initialization is done which value initializes all subobjects. Value initialization of built-in types mean they get a suitable zero value while default initialization of built-in types means they are left uninitialized.
Since A has an explicitly written default constructor, its constructor needs to initialize all members which aren't of class type with a default constructor. B has an implicitly written default constructor and default or value initialization is performed as necessary for the subobjects.
The objects in a std::vector<T> are constructed using T() (if no other arguments are provides as is the case, e.g., for push_back() or emplace_back()). For A members that means they are left uninitialized, for B members that means they are zero initialized.

When creating a vector with a specific size, the vector doesn't set default values, it uses something called value initialization which is something completely different.
For primitive types, like e.g. int, that means the value will be zero-initialized (i.e. zero). But for objects with constructors the default constructor (if it has one) will be used.
In the case of your A class, the default constructor does not initialize the i member variable, so it will be uninitialized and have an indeterminate value, by printing that value you have undefined behavior.
As for the B class it's a POD type which means that value initialization of the whole object will also value-initialize all members. B is a POD type because it has a trivial default constructor (which A does not have).

A() {};
You are not initializing A::i in A() so its value is unspecified after value initialization. This behaviour is different to what you would get with a compiler-provided default constructor.
A() = default;
Defining as default on the other hand has the effect of providing a constructor with the same semantics of the compiler-synthesized default constructor. That is to say, A::i would get value-initialized (and therefore zero-initialized) when an A object is value initialized with expressions such as A() or A{}.
Note: this can be fixed by either dropping the definition of the default constructor, define it as default, or explicitly initialize i.
struct A
{
// Compiler provided A() will initialize i
// when A is value initialized
int i;
};
or, equivalently in terms of initialization semantics,
struct A
{
int i;
A() = default; // useful if other constructors defined
};

An "empty" constructor will not initialize anything, so the fact that va contains zero is just luck/coincidence. The default constructor will do nothing either, so they will produce the same thing. What you are seeing is undefined behaviour (or unspecified behaviour perhaps - as we shouldn't really expect world war three to break out because of an uninitialized variable).

The problem is that std::vector is supposed to call the default constructor & not set the values by default. The default constructor of int sets it's value to 0 hence you get a std::vector<int> with all values initialized with 0. However you default constructor of A is empty & hence it doesn't initialize i with any value. If you define the constructor as :-
A() { i = 0; }
Then your vector would definitely initialize the i of the objects of A with 0. What default does is this :-
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A()
{
cout << "Default constructed A\n";
}
A (A &obj)
{
cout << "Copy constructed A\n";
}
};
class X
{
int x;
double d;
bool b;
A obj;
public:
X() = default;
void show()
{
cout << fixed << x << ' ' << d << ' ' << b << '\n';
}
};
int main()
{
cout << boolalpha;
vector<X> v(1);
v[0].show();
return 0;
}
/* Output :-
Default constructed A
0 0.000000 false
*/
What B() = default; roughly means as far I get from this test code that it is a replacement for :-
B()
{
i = 0;
d = 0.00;
b = false;
// obj is default initialized
}
ie the initialization of the data members with their default values (for primitives) or default constructors (for classes). But this is only for runtime initialization which means for locally created objects the definition is same as B() = {}.
So the thing was that class A had a user defined constructor (with an empty body) & hence didn't initialize because that's how the programmer defined it. On the other hand class B had a constructor that was left on to the compiler to call implicitly. Hence for local objects it didn't do any specific initialization but for dynamic objects (like that of a std::vector) it did the value type initialization.

Related

C++ object initialization with copy-list-initializer

// Example program
#include <iostream>
#include <string>
class T{
public:
int x, y;
T(){
std::cout << "T() constr called..." << std::endl;
};
T(int x, int y):x(x),y(y){
std::cout << "T(x,y) constr called..." << std::endl;
}
void inspect(){
std::cout << "T.x: " << this->x << std::endl;
std::cout << "T.y: " << this->y << std::endl;
}
};
int main()
{
T t1(5,6);
t1.inspect();
std::cout << std::endl;
T t2 = {};
t2.inspect();
}
I am getting the following result:
T(x,y) constr called...
T.x: 5
T.y: 6
T() constr called...
T.x: 208787120
T.y: 31385
The members of t2 instance were not zero initialized (what I wanted to achieve). Do I understand it correctly, that if I have a constructor defined, it will not perform a zero initialization?
(I know how to achieve initialization to zero using explicit default values. The problem is why I am not able to do it using the init-list)
List initialization
Otherwise, if the braced-init-list is empty and T is a class type with a default constructor, value-initialization is performed.
Value-initialization
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
Aggregate-initialization (it seems this is not my case and therefore it is not initializing members to zero)
An aggregate is one of the following types:
class type (typically, struct or union), that has
no user-declared constructors
What would be the simplest and less error-prone modification of legacy code, where I need to solve issues where some class members are used before they are initialized?
Do I understand it correctly, that if I have a constructor defined, it will not perform a zero initialization?
Yes.
Note that T is not an aggregate because it contains user-provided constructors. As the effect of value initialization:
if T is a class type with no default constructor or with a
user-provided or deleted default constructor, the object is
default-initialized;
if T is a class type with a default constructor that is neither
user-provided nor deleted (that is, it may be a class with an
implicitly-defined or defaulted default constructor), the object is
zero-initialized and then it is default-initialized if it has a
non-trivial default constructor;
T contains a user-provided default constructor, then #1 (but not #2 performing zero-initialization firstly) is applied.
In default initialization, the user-provided default constructor is used to initialize the object. The default constructor doesn't perform initialization on data members, they are initialized to indeterminate values.
The data members of t2 have garbage value. This is because they are of built-in type and you did not intialized them explicitly. The solution would be to:
Solution 1: Use constructor initializer list
T(): x(0), y(0){
std::cout << "T() constr called..." << std::endl;
};
Solution 2: Use in-class initializer
int x = 0, y = 0;
This is why it is advised that:
always initialize built-in type in block/local scope
If you use any of the above given solution, the output will be:
T(x,y) constr called...
T.x: 5
T.y: 6
T() constr called...
T.x: 0
T.y: 0
which is what you want and can be seen here and here.
Another solution would be to use delegating constructor (as suggested by #MarekR in the comment below) like:
T():T(0, 0)
{
std::cout << "T() constr called..." << std::endl;
}
Zero initialization is a special case. The standard only guarantees member attributes to be default initialized. For class objects it indeed means that the default constructor will be called. But for basic type objects default initialization is just... no initialization at all. You have to explicitely ask for it if you need it:
T(): x(0), y(0) {
...

How unused default member initializer can change program behavior in C++?

Please consider this short code example:
#include <iostream>
struct A
{
A() { std::cout << "A() "; }
~A() { std::cout << "~A() "; }
};
struct B { const A &a; };
struct C { const A &a = {}; };
int main()
{
B b({});
std::cout << ". ";
C c({});
std::cout << ". ";
}
GCC prints here ( https://gcc.godbolt.org/z/czWrq8G5j )
A() ~A() . A() . ~A()
meaning that the lifetime of A-object initializing reference in b is short, but in c the lifetime is prolonged till the end of the scope.
The only difference between structs B and C is in default member initializer, which is unused in main(), still the behavior is distinct. Could you please explain why?
C c(...); is syntax for direct initialisation. Overload resolution would find a match from the constructors of C: The move constructor can be called by temporary materialisation of a C from {}. {} is value initialisation which will use the default member initialiser. Thus, the default member initialiser isn't unused. Since C++17, the move constructor isn't necessary and {} initialises variable c directly; In this case c.a is bound directly to the temporary A and the lifetime is extended until destruction of C.
B isn't default constructible, so the overload resolution won't find a match. Instead, aggregate initialisation is used since C++20 - prior to that it would be ill-formed. The design of the C++20 feature is to not change behaviour of previously valid programs, so aggregate initialisation has lower priority than the move constructor.
Unlike in the case of C, the lifetime of the temporary A isn't extended because parenthesised initialisation list is an exceptional case. It would be extended if you used curly braces:
B b{{}};

Why does a user-provided default constructor lead to an uninitialized member?

Please consider the following code:
#include <iostream>
struct A{ // with implicit default constructor
int number;
};
struct B{
int number;
B(){}; // user-provided default constructor
};
int main()
{
A aa = {};
B bb = {};
std::cout << "aa.number: " << aa.number << std::endl;
std::cout << "bb.number: " << bb.number << std::endl;
}
Running the code online
results in the following output:
aa.number: 0
bb.number: 19715
Why is bb.number uninitialized?
I thought that zero initialisation is guaranteed by using ={} ?
I thought that zero initialisation is guaranteed by using ={} ?
That is only true if the type is "correct", which B is not. B bb = {}; will default construct a B and your default constructor, B(){};, doesn't initialize number so no matter what, number will never be initialized because that is how your default constructor works. If you had a "correct" constructor like
B() : number(0) {};
// or use
int number = 0;
B(){};
Then you'd get zero initialization of number when it is default constructed.
This isn't the case with A because A is an aggregate and those come with certain guarantees like zero initialization if an empty braced-init-list, technical name for the {}, is used.
A is an aggregate type, because it doesn't have any user-provided constructors (and fulfills a few other requirements).
Therefore A aa = {}; does not call the implicitly generated default constructor. Instead initialization with brace-enclosed initializer lists performs aggregate initialization, which for an empty list means that the members are initialized as if by a {} initializer, which in turn means for a member of scalar type such as int that it will be initialized to zero.
B is not an aggregate type, because it does have a user-provided constructor.
Therefore B bb = {}; cannot do aggregate initialization and will call the default constructor instead. The default constructor (in either A or B) does not specify an initializer for the members and so the member is default-initialized, which for a fundamental type, such as int, means that it will not be set to any value. Its value will remain indeterminate.
Accessing the indeterminate value, which your program does, causes undefined behavior.
If you declare a constructor yourself, then it becomes that constructor's responsibility to initialize all the members appropriately. The rule that = {} or {} always initializes only holds under the assumption that a user-provided default constructor, if it exists, does the right thing in the sense that it provides sensible initializers to all its members and it doesn't have to mean zero-initialization necessarily.
struct B has a user-provided constructor, so it doesn't get the default constructor that can initialize the members to zero. Your user-provided constructor replaces that, so you have to do the work yourself.

C++11 member variable of reference type, different behaviour after vector push_back

I was using somebody else's class which was acting odd when I pushed it into a vector. It involves a member variable which is a reference to another member variable. Here is the smallest self-contained example:
#include <iostream>
#include <vector>
class Myclass {
public:
Myclass() : a(1.0) {}
float a;
float &a_ref = a;
void addOne() {
a = a + 1.0;
}
};
int main() {
Myclass instance1;
instance1.addOne();
//prints 2:
std::cout << "instance.a_ref is " << instance1.a_ref << std::endl;
std::vector<Myclass> vec;
Myclass instance2;
vec.push_back(instance2);
vec.at(0).addOne();
//prints 1;
std::cout << "vec.at(0).a_ref is " << vec.at(0).a_ref << std::endl;
return 0;
}
I was compiling with g++ and -std=c++11, so I didn't notice the problem for a while. I see now the issue is probably to do with the synthesised copy constructor and the reference member. But what I'm not sure about is:
Why is there different behaviour when the object is in a vector ?
Why does g++ not give any warnings about this, using c++11 standard ?
Bonus question because I'm curious:
What is initialized first, a or a_ref?
The problem is indeed with the defaulted copy constructor. The defaulted copy constructor initialises all members from the members of the source object. That is, the defaulted copy constructor is identical to this:
Myclass(const Myclass &src) :
a(src.a),
a_ref(src.a_ref)
{}
The defaulted copy constructor initialises all members, so it ignores any in-class initialisers.
This is also why pushing into a vector causes the problem. vec.at(0) was created as a copy of instance2, which means vec.at(0).a_ref refers to instance2.a. You could easily verify this by printing their addresses (live example).
The implicitly defined copy/move constructor:
[...] performs a performs a memberwise copy/move of its bases and members. [ Note: brace-or-equal-initializers of non-static data members are ignored. [...]
In particular, reference members are direct-initialized to refer to the same object the corresponding reference member in the source object refers to.
So in your case, vec.at(0).a_ref refers to the member a of instance2.
This is not detected by the compiler because in general reference members are expected to refer to a longer-lived object outside the class.

Put a parenthesis after member variable to initialize?

I have seen people put a parenthesis after the member variable in the initialization list. I wonder why would people do that?
For example, I have a STL container in header file:
class A{
public:
A();
...
private:
vector<string> v;
}
and in source file:
A::A() : v() {}
My question is what is v() and why do people do that since that doesn't look like v is initialized into a value either
That will run the default constructor or initializer (for plain types) for the member. In this context, it will default construct the vector. Since it is the default constructor, it is not necessary here. v would have been default constructed in the absence of an initializer.
class Example {
private:
int defaultInt;
vector<int> defaultVector;
int valuedInt;
vector<int> sizedVector;
public:
Example(int value = 0, size_t vectorLen = 10)
: defaultInt(), defaultVector(), valuedInt(value), sizedVector(vectorLen)
{
//defaultInt is now 0 (since integral types are default-initialized to 0)
//defaultVector is now a std::vector<int>() (a default constructed vector)
//valuedInt is now value since it was initialized to value
//sizedVector is now a vector of 'size' default-intialized ints (so 'size' 0's)
}
};
For kicks and giggles, you could also do thirdVector(vectorLen, value) to get a vector with vectorLen elements with the value value. (So Example(5, 10) would make thirdVector a vector of 10 elements valued 5.)
My question is what is v() and why do people do that since that doesn't look like v is initialized into a value either
This is sometimes done to be more explicit. For non POD types this is not necessary as the default constructor is automatically called for them. If the types default constructor has not been defined or is not accessible, this will cause a compile error.
This makes the most sense for POD types, as their value is undefined when they are not initialized.
struct A
{
int t;
A() : { /* value of t undefined */ }
}
struct A
{
int t;
A() : t() { /* value of t is t's default value of 0 */ }
}