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{{}};
Related
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 know that a temporary cannot be bound to a non-const reference, but it can be bound to const reference. That is,
A & x = A(); //error
const A & y = A(); //ok
I also know that in the second case (above), the lifetime of the temporary created out of A() extends till the lifetime of const reference (i.e y).
But my question is:
Can the const reference which is bound to a temporary, be further bound to yet another const reference, extending the lifetime of the temporary till the lifetime of second object?
I tried this and it didn't work. I don't exactly understand this. I wrote this code:
struct A
{
A() { std::cout << " A()" << std::endl; }
~A() { std::cout << "~A()" << std::endl; }
};
struct B
{
const A & a;
B(const A & a) : a(a) { std::cout << " B()" << std::endl; }
~B() { std::cout << "~B()" << std::endl; }
};
int main()
{
{
A a;
B b(a);
}
std::cout << "-----" << std::endl;
{
B b((A())); //extra braces are needed!
}
}
Output (ideone):
A()
B()
~B()
~A()
-----
A()
B()
~A()
~B()
Difference in output? Why the temporary object A() is destructed before the object b in the second case? Does the Standard (C++03) talks about this behavior?
The standard considers two circumstances under which the lifetime of a temporary is extended:
§12.2/4 There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. The first context is when an expression appears as an initializer for a declarator defining an object. In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete. [...]
§12.2/5 The second context is when a reference is bound to a temporary. [...]
None of those two allow you to extend the lifetime of the temporary by a later binding of the reference to another const reference. But ignore the standarese and think of what is going on:
Temporaries are created in the stack. Well, technically, the calling convention might mean that a returned value (temporary) that fits in the registers might not even be created in the stack, but bear with me. When you bind a constant reference to a temporary the compiler semantically creates a hidden named variable (that is why the copy constructor needs to be accessible, even if it is not called) and binds the reference to that variable. Whether the copy is actually made or elided is a detail: what we have is an unnamed local variable and a reference to it.
If the standard allowed your use case, then it would mean that the lifetime of the temporary would have to be extended all the way until the last reference to that variable. Now consider this simple extension of your example:
B* f() {
B * bp = new B(A());
return b;
}
void test() {
B* p = f();
delete p;
}
Now the problem is that the temporary (lets call it _T) is bound in f(), it behaves like a local variable there. The reference is bound inside *bp. Now that object's lifetime extends beyond the function that created the temporary, but because _T was not dynamically allocated that is impossible.
You can try and reason the effort that would be required to extend the lifetime of the temporary in this example, and the answer is that it cannot be done without some form of GC.
No, the extended lifetime is not further extended by passing the reference on.
In the second case, the temporary is bound to the parameter a, and destroyed at the end of the parameter's lifetime - the end of the constructor.
The standard explicitly says:
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.
§12.2/5 says “The second context [when the lifetime of a temporary
is extended] is when a reference is bound to a temporary.” Taken
literally, this clearly says that the lifetime should be extended in
your case; your B::a is certainly bound to a temporary. (A reference
binds to an object, and I don't see any other object it could possibly
be bound to.) This is very poor wording, however; I'm sure that what is
meant is “The second context is when a temporary is used to
initialize a reference,” and the extended lifetime corresponds to
that of the reference initiailized with the rvalue expression creating
the temporary, and not to that of any other references which may later
be bound to the object. As it stands, the wording requires something
that simply isn't implementable: consider:
void f(A const& a)
{
static A const& localA = a;
}
called with:
f(A());
Where should the compiler put A() (given that it generally cannot see
the code of f(), and doesn't know about the local static when
generating the call)?
I think, actually, that this is worth a DR.
I might add that there is text which strongly suggests that my
interpretation of the intent is correct. Imagine that you had a second
constructor for B:
B::B() : a(A()) {}
In this case, B::a would be directly initialized with a temporary; the
lifetime of this temporary should be extended even by my interpretation.
However, the standard makes a specific exception for this case; such a
temporary only persists until the constructor exits (which again would
leave you with a dangling reference). This exception provides a very
strong indication that the authors of the standard didn't intend for
member references in a class to extend the lifetime of any temporaries
they are bound to; again, the motivation is implementability. Imagine
that instead of
B b((A()));
you'd written:
B* b = new B(A());
Where should the compiler put the temporary A() so that it's lifetime
would be that of the dynamically allocated B?
Your example doesn't perform nested lifetime extension
In the constructor
B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }
The a_ here (renamed for exposition) is not a temporary. Whether an expression is a temporary is a syntactic property of the expression, and an id-expression is never a temporary. So no lifetime extension occurs here.
Here's a case where lifetime-extension would occur:
B() : a(A()) { std::cout << " B()" << std::endl; }
However, because the reference is initialized in a ctor-initializer, the lifetime is only extended until the end of the function. Per [class.temporary]p5:
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.
In the call to the constructor
B b((A())); //extra braces are needed!
Here, we are binding a reference to a temporary. [class.temporary]p5 says:
A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
Therefore the A temporary is destroyed at the end of the statement. This happens before the B variable is destroyed at the end of the block, explaining your logging output.
Other cases do perform nested lifetime extension
Aggregate variable initialization
Aggregate initialization of a struct with a reference member can lifetime-extend:
struct X {
const A &a;
};
X x = { A() };
In this case, the A temporary is bound directly to a reference, and so the temporary is lifetime-extended to the lifetime of x.a, which is the same as the lifetime of x. (Warning: until recently, very few compilers got this right).
Aggregate temporary initialization
In C++11, you can use aggregate initialization to initialize a temporary, and thus get recursive lifetime extension:
struct A {
A() { std::cout << " A()" << std::endl; }
~A() { std::cout << "~A()" << std::endl; }
};
struct B {
const A &a;
~B() { std::cout << "~B()" << std::endl; }
};
int main() {
const B &b = B { A() };
std::cout << "-----" << std::endl;
}
With trunk Clang or g++, this produces the following output:
A()
-----
~B()
~A()
Note that both the A temporary and the B temporary are lifetime-extended. Because the construction of the A temporary completes first, it is destroyed last.
In std::initializer_list<T> initialization
C++11's std::initializer_list<T> performs lifetime-extension as if by binding a reference to the underlying array. Therefore we can perform nested lifetime extension using std::initializer_list. However, compiler bugs are common in this area:
struct C {
std::initializer_list<B> b;
~C() { std::cout << "~C()" << std::endl; }
};
int main() {
const C &c = C{ { { A() }, { A() } } };
std::cout << "-----" << std::endl;
}
Produces with Clang trunk:
A()
A()
-----
~C()
~B()
~B()
~A()
~A()
and with g++ trunk:
A()
A()
~A()
~A()
-----
~C()
~B()
~B()
These are both wrong; the correct output is:
A()
A()
-----
~C()
~B()
~A()
~B()
~A()
In your first run, the objects are destroyed in the order they were pushed on the stack -> that is push A, push B, pop B, pop A.
In the second run, A's lifetime ends with the construction of b. Therefore, it creates A, it creates B from A, A's lifetime finishes so it is destroyed, and then B is destroyed. Makes sense...
I don't know about standards, but can discuss some facts which I saw in few previous questions.
The 1st output is as is for obvious reasons that a and b are in the same scope. Also a is destroyed after b because it's constructed before b.
I assume that you should be more interested in 2nd output. Before I start, we should note that following kind of object creations (stand alone temporaries):
{
A();
}
last only till the next ; and not for the block surrounding it. Demo. In your 2nd case, when you do,
B b((A()));
thus A() is destroyed as soon as the B() object creation finishes. Since, const reference can be bind to temporary, this will not give compilation error. However it will surely result in logical error if you try to access B::a, which is now bound to already out of scope variable.
§12.2/5 says
A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.
Pretty cut and dried, really.
I am wondering that object construction with assignment operator works, never seen that before I saw this question:
return by value calls copy ctor instead of move
Reduced example code:
class A
{
public:
int x;
A(int _x):x(_x){ std::cout << "Init" << std::endl;}
void Print() { std::cout << x << std::endl; }
A& operator = ( const int ) = delete;
};
int main()
{
A a=9;
a.Print();
}
Is writing of
A a(9);
A a{9};
A a=9;
all the same?
This has nothing to do with assignment operator, it's initialization, more precisely copy initialization, which just uses the equals sign in the initializer.
when a named variable (automatic, static, or thread-local) of a non-reference type T is declared with the initializer consisting of an equals sign followed by an expression.
For A a = 9; the approriate constructor (i.e. A::A(int)) will be invoked to construct a. 1
A a(9); is direct initialization, A a{9}; is direct list initialization (since C++11), they all cause the A::A(int) to be invoked to construct the object for this case. 2
1 Before C++17 the appropriate move/copy constructor is still required conceptually. Even though it might be optimized out but still has to be accessible. Since C++17 this is not required again.
2 Note that there're still subtle differences among these initialization styles, they may lead to different effects in some specialized cases.
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.
I don't understand the difference between the two following statements:
Thing thing;
Thing thing = Thing();
Both create a Thing object and put it in the variable thing, right? If so, two questions:
1- What are the technical differences between the two?
2- When should I use one over the other?
Please note:
A- I am not using C++ 11.
B- New to C++, please use newbie-friendly words.
Thing thing;
is default-initialization. If Thing is a class type, it calls the default constructor of Thing, and that's it.
Thing thing = Thing();
value-initializes a temporary Thing and then copies/moves that temporary into thing. In practice compilers will elide the copy/move, making it effectively a value-initialization, but this still requires a copy/move constructor to be available. The = Thing(); syntax is needed to get value-initialization semantics because Thing thing(); is the vexing parse.*
The difference between value-initialization and default-initialization is that in some cases (the exact cases depend on the version of the standard, but a non-union class type with no user-provided constructors, and non-class non-array types, along with arrays of these types qualify in all versions), value-initialization will zero-initialize first before calling the default constructor.
Using the second version is helpful if Thing can be a non-class (e.g., in a template) or is a class type that would get the zero-initialization treatment (e.g., a POD class) and you want it to have well-defined values.
*Thing thing{}; is subtly different in initialization semantics from plain value-initialization in C++14.
The notation
Thing thing = Thing();
which does a copy initialization, could be used in C++03 template code to effectively default-construct a Thing. The thing is that this works also for built-in types Thing such as int. This contorted notation because writing Thing thing(); just declares a function (a special case of “the most vexing parse”).
In C++11 one can instead write
Thing thing{};
An alternative technique in C++03 was to wrap Thing in a struct, like this:
struct Initialized_thing
{
Thing thing;
Initialized_thing(): thing() {}
};
The difference is that the compiler can issue an error for the second definition if the copy or move constructor is not accessible. For example they might be defined as private or deleted.
Thing thing;
Thing thing = Thing(); // there may be a compiler error
For example consider the following programs
int main()
{
class A
{
private:
A( const A & );
public:
A() {}
};
A a;
return 0;
}
The program above will be compiled successfuly while the program below will not be compiled (if only the compiler is not for example MS VC++ 2010)
int main()
{
class A
{
private:
A( const A & );
public:
A() {}
};
A a = A();
return 0;
}
The other difference that in this statement
A a;
a is default initialized
while in this statement
A a = A();
a is value initialized.
From the C++ Standard
2 The expression T(), where T is a simple-type-specifier or
typename-specifier for a non-array complete object type or the
(possibly cv-qualified) void type, creates a prvalue of the specified
type, whose value is that produced by value-initializing (8.5) an
object of type T; no initialization is done for the void() case.
Consider another example
int main()
{
class A
{
int x;
};
A a1;
A a2 = A();
return 0;
}
For a1 value of x will be unspecified while for s2 value of x will be equal to 0.
As others have written the syntax
Thing obj;
will default-initialize your object, while the syntax
Thing obj = Thing();
will value-initialize your object.
The second syntax causes more stuff to be involved:
A move/copy constructor should be accessible
A temporary is value-initialized
Copy elision takes place
They usually serve different purposes and although the first one is the most commonly used to initialize an object, the latter is different in the value-initialization meaning.
I recommend reading the documentation for the differences between value and default initialization, anyway a simple example to show the differences is the following:
class Thing {
public:
int data;
Thing() {
cout << "default ctor" << endl;
}
Thing(const Thing&) {
cout << "copy ctor" << endl;
}
const Thing& operator=(const Thing&) {
cout << "operator=" << endl;
return *this;
}
};
struct Thingy {
int data;
};
int main() {
cout << is_pod<Thing>::value << endl; // Thing is NOT pod
Thing thing; // Calls default constructor
cout << thing.data << endl; // Garbage
Thing thing2 = Thing(); // Copy elision is able to optimize this (standard-defined 12.8/31). Anyway a copy/move constructor must be accessible
cout << thing2.data << endl; // Garbage
// -----------------------------------------------------
cout << is_pod<Thingy>::value << endl; // Thingy IS pod
Thingy thingy1;
cout << thingy1.data << endl; // garbage
Thingy thingy2 = Thingy();
cout << thingy2.data << endl; // zero-initialized!
}
The above code will print
0 // Not a POD
default ctor
-1326900592 // garbage
default ctor
-1326900592 // garbage
// ---------------------------
1 // POD
-1326900592 // garbage
0 // zero-initialized