What is the behavior of the compiler's ctor? - c++

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.

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) {
...

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.

std::vector initialization of class member

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.

2 different types of constructor invocation from copy constructor

Consider the sample code below:
#include <iostream>
using namespace std;
class core
{
public:
core(const core& obj)
{
cout << "core copy ctor called\n";
}
core()
{
cout << "core default ctor called\n";
}
};
class sample : public core
{
public:
sample()
{
cout << "sample default ctor called\n";
}
#if 0
sample(const sample& obj)
{
cout << "sample copy ctor called\n";
}
#endif
};
int main()
{
sample s1;
sample s2 = s1; //Line1
return 0;
}
Type1: Copy constructor not declared explicitly for class sample
(Type1 is shown in the code above. Then the copy constructor of class sample is implicitly generated by the compiler).
When the statement, Line1 is executed, first the copy constructor of class core is invoked, and then the copy constructor of class sample is invoked.
Type2: Copy constructor defined explicitly for class sample
When the statement, Line1 is executed, first the default constructor of class core is invoked, and then the copy constructor of class sample is invoked.
Question:
Why is this difference in behavior for copy constructor as mentioned in Type1 and Type2 ?
Because the copy constructor you explicitly define for sample doesn't ask for core's copy constructor to be invoked. You'd have to write : core(obj) if you wanted to make that happen.
Put another way, when you write an explicit copy constructor, you are taking charge of the copy construction of sample, including its core sub-object. By writing it the way you have done, you have chosen not to initialise the core sub-object using obj. Since you haven't said how you do want it initialised, the compiler just uses core's default constructor instead, hence the behaviour in the second case you outline.
By contrast, in the first case, the compiler-generated default copy constructor for sample does ask for the core sub-object to be initialised using core's copy constructor, hence the observed behaviour.

What are all the member-functions created by compiler for a class? Does that happen all the time?

What are all the member-functions created by compiler for a class? Does that happen all the time? like destructor.
My concern is whether it is created for all the classes, and why is default constructor needed?
C++98/03
If they are needed,
the compiler will generate a default constructor for you unless you declare any constructor of your own.
the compiler will generate a copy constructor for you unless you declare your own.
the compiler will generate a copy assignment operator for you unless you declare your own.
the compiler will generate a destructor for you unless you declare your own.
As Péter said in a helpful comment, all those are only generated by the compiler when they are needed. (The difference is that, when the compiler cannot create them, that's Ok as long as they aren't used.)
C++11
C++11 adds the following rules, which are also true for C++14 (credits to towi, see this comment):
The compiler generates the move constructor if
there is no user-declared copy constructor, and
there is no user-declared copy assignment operator, and
there is no user-declared move assignment operator and
there is no user-declared destructor,
it is not marked deleted,
and all members and bases are moveable.
Similarly for move assignment operator, it is generated if
there is no user-declared copy constructor, and
there is no user-declared copy assignment operator, and
there is no user-declared move constructor and
there is no user-declared destructor,
it is not marked deleted,
and all members and bases are moveable.
Note that these rules are a bit more elaborate than the C++03 rules and make more sense in practice.
For an easier understanding of what is what in the above:
class Thing {
public:
Thing(); // default constructor
Thing(const Thing&); // copy c'tor
Thing& operator=(const Thing&); // copy-assign
~Thing(); // d'tor
// C++11:
Thing(Thing&&); // move c'tor
Thing& operator=(Thing&&); // move-assign
};
Further reading: if you are a C++-beginner consider a design that does not require you to implement any of five a.k.a The Rule Of Zero originally from an article written by Martinho Fernandes.
Do you mean 'defined' by 'created'?
$12.1 - "The default constructor (12.1), copy constructor and copy assignment operator (12.8), and destructor (12.4) are special member functions.
If 'created' means 'defined' then, here are the important parts from the C++ Standard.
-An implicitly-declared default constructor for a class is implicitly defined when it is used to create an object of its class type (1.8).
-If a class has no user-declared destructor, a destructor is declared implicitly. An implicitly-declared destructor is implicitly defined when it is used to destroy an object of its class type.
-If the class definition does not explicitly declare a copy constructor, one is declared implicitly. An implicitly-declared copy constructor is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type).
-If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type.
C++17 N4659 standard draft
https://github.com/cplusplus/draft/blob/master/papers/n4659.pdf 6.1 "Declarations and definitions" has a note which likely summarizes all of them:
3
[ Note: In some circumstances, C ++ implementations implicitly define the default constructor (15.1), copy
constructor (15.8), move constructor (15.8), copy assignment operator (15.8), move assignment operator (15.8),
or destructor (15.4) member functions. — end note ] [ Example: Given
#include <string>
struct C {
std::string s; // std::string is the standard library class (Clause 24)
};
int main() {
C a;
C b = a;
b = a;
}
the implementation will implicitly define functions to make the definition of C equivalent to
struct C {
std::string s;
C() : s() { }
C(const C& x): s(x.s) { }
C(C&& x): s(static_cast<std::string&&>(x.s)) { }
// : s(std::move(x.s)) { }
C& operator=(const C& x) { s = x.s; return *this; }
C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
// { s = std::move(x.s); return *this; }
~ C() { }
};
— end example ]
The conditions under which those are declared are explained at: Conditions for automatic generation of default/copy/move ctor and copy/move assignment operator?
A cool way to ensure that something has a default is to try and make it use = default as explained at: What does "default" mean after a class' function declaration?
The example below does that, and also exercises all the implicitly defined functions.
#include <cassert>
#include <string>
struct Default {
int i;
Default() = default;
Default(const Default&) = default;
Default& operator=(Default&) = default;
Default& operator=(const Default&) = default;
Default(Default&&) = default;
Default& operator=(Default&&) = default;
~Default() = default;
};
struct Instrument {
int i;
static std::string last_call;
Instrument() { last_call = "ctor"; }
Instrument(const Instrument&) { last_call = "copy ctor"; }
Instrument& operator=(Instrument&) { last_call = "copy assign"; return *this; }
Instrument& operator=(const Instrument&) { last_call = "copy assign const"; return *this; }
Instrument(Instrument&&) { last_call = "move ctor"; }
Instrument& operator=(Instrument&&) { last_call = "move assign"; return *this; }
~Instrument() { last_call = "dtor"; }
};
std::string Instrument::last_call;
int main() {
// See what the default constructors are doing.
{
// Default constructor.
Default ctor;
// i is uninitialized.
// std::cout << ctor.i << std::endl;
ctor.i = 1;
// Copy constructor.
Default copy_ctor(ctor);
assert(copy_ctor.i = 1);
// Copy assignment.
Default copy_assign;
copy_assign = ctor;
assert(copy_assign.i = 1);
// Copy assignment const.
const Default const_ctor(ctor);
Default copy_assign_const;
copy_assign_const = const_ctor;
assert(copy_assign_const.i == 1);
// Move constructor.
Default move_ctor(std::move(ctor));
assert(move_ctor.i == 1);
// Move assignment.
Default move_assign;
move_assign = std::move(ctor);
assert(move_assign.i == 1);
}
// Check that the constructors are called by these calls.
{
// Default constructor.
Instrument ctor;
assert(Instrument::last_call == "ctor");
// Copy constructor.
Instrument copy_ctor(ctor);
assert(Instrument::last_call == "copy ctor");
// Copy assignment.
copy_ctor = ctor;
assert(Instrument::last_call == "copy assign");
// Copy assignment const.
const Instrument const_ctor(ctor);
Instrument copy_assign_const;
copy_assign_const = const_ctor;
assert(Instrument::last_call == "copy assign const");
// Move constructor.
Instrument move_ctor(std::move(ctor));
assert(Instrument::last_call == "move ctor");
// Move assignment.
Instrument move_assign;
move_assign = std::move(ctor);
assert(Instrument::last_call == "move assign");
// Destructor.
{
Instrument dtor;
}
assert(Instrument::last_call == "dtor");
}
}
GitHub upstream.
Tested with GCC 7.3.0:
g++ -std=c++11 implicitly_defined.cpp
By default, if not implemented by the user, the compiler add some member functions to the class. Those are called the big four :
default constructor
copy constructor
copy operator (assignment)
destructor
Depending on the types of the members and which member function listed you provide yourself, those will not all be generated.
Other answers have told you what's created, and that the compiler may only generate them if used.
My concern is whether it is created for all the classes...
Why concerned? Thinking it's creating unwanted code in the executable? Unlikely, but you can check easily enough with your environment.
Or perhaps your concern was that it might not create a constructor when you want one? Nothing to worry about... they're always created if needed and not provided by the user.
...and why is default constructor needed?
Because classes may have objects inside them with their own destructors that need to be systematically invoked. For example, given...
struct X
{
std::string a;
std::string b;
};
...the default destructor makes sure the destructors for a and b run.