// 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) {
...
Related
I happen to come across this, and I don't get it, why Foo c{a} called the 2 constructors. I know that every time an object is created, the constructor, is called, so why does it called the foo(std::initializer_list<Foo>) even if he did not do this Foo c{{a}} or Foo c({a})?
struct Foo {
Foo() {}
Foo(std::initializer_list<Foo>) {
std::cout << "initializer list" << std::endl;
}
Foo(const Foo&) {
std::cout << "copy ctor" << std::endl;
}
};
int main() {
Foo a;
Foo b(a); // copy ctor
Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}
If we look at https://en.cppreference.com/w/cpp/language/direct_initialization
Here we see:
T object { arg }; (2) (since C++11)
initialization of an object of non-class type with a single brace-enclosed initializer (note: for class types and other uses of braced-init-list, see list-initialization)
In https://en.cppreference.com/w/cpp/language/list_initialization there is following bullet:
If T is an aggregate class and the initializer list has a single element of the same or derived type (possibly cv-qualified), the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization). (since C++14)
And returning to direct initialization:
the constructors of T are examined and the best match is selected by overload resolution. The constructor is then called to initialize the object.
So, it seems that in this situation clang is right to call copy constructor and GCC is wrong. At least since C++14.
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.
I tested the following code on two different compilers, I could not determine how the object w2 is constructed.
#include<iostream>
using namespace std;
class Widget
{
public:
Widget()
{
std::cout <<count++<<" "<< __FUNCTION__ << "()"<< std::endl;
}
Widget(std::initializer_list<int> il)
{
std::cout <<count++<< " " <<__FUNCTION__ << "(std::initializer_list<int> il)" << std::endl;
}
private:
static int count;
};
int Widget::count = 0;
int main()
{
Widget w1();
Widget w2{};
Widget w3{ 10, 5 };
Widget w4({});
}
the output on the two compilers was:
0 Widget()
1 Widget(std::initializer_list<int> il)
2 Widget(std::initializer_list<int> il)
I could not determine how the object w2 is constructed.
w2 is constructed by the default constructor; as you can see from the output
0 Widget()
Firstly, Widget is not an aggregate type, it has user-defined constructors; then Widget w2{}; performs 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.
If T is a class type that has no default constructor but has a
constructor taking std::initializer_list, list-initialization is
performed.
Both above cases are false here. Then
1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;
then in default initialization,
(emphasis mine)
if T is a non-POD (until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;
BTW: Widget w1(); is not a variable definition, but a function declaration; which declares a function named w1 taking nothing and returning Widget. That's why you only get 3 outputs. You might want Widget w1;.
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.
If I define a class's ctor and dtor like so,
struct Test
{
//Test(){}
//~Test(){}
int a;
}
int main()
{
Test t;
std::cout << t.a << std::endl;
//^Prints garbage if ctor/dtor are defined, 0 if commented out.
}
Why is a 0 in one case and garbage in the other?
[class.ctor]/6
... The implicitly-defined default constructor performs the set of initializations of the class that would be
performed by a user-written default constructor for that class with no ctor-initializer (12.6.2) and an empty
compound-statement. ...
In other words, the constructor that the compiler generates is the same as
Test() {}
Since a is not explicitly initialized by the constructor, a is left uninitialized.