In my current understanding, roughly speaking, (non-static) local variables are destroyed when the thread of execution leaves their scope. However, I found that the exact points when the local variables are destroyed in different situations differ. I've been struggling with understanding how to determine when local variables are destroyed, and, in particular, the reason behind it. (possible related questions I asked include: 1, 2, 3)
Given 2 toy classes:
class A {
public:
A() { cout << "A created\n"; }
A(const A&) { cout << "A copied\n"; }
~A() { cout << "A destroyed" << endl; }
};
class B{
public:
B() { cout << "B created\n"; }
B(const B&) { cout << "B copied\n"; }
~B() { cout << "B destroyed\n"; }
};
Example 1:
B& f() {
A a;
static B b;
return b;
}
int main()
{
B b = f();
}
here the outputs are as follows:
A created
B created
A destroyed
B copied
B destroyed
B destroyed
In my current understanding, in example 1, the local (non-static) variable(s) are destroyed immediately after the reference is returned, then the object being referenced is copied to b.
Example 2:
B g() {
A a;
B b;
return b;
}
int main()
{
B b = g();
}
here the outputs are as follows:
A created
B created
B copied
B destroyed
A destroyed
B destroyed
this time, the copying to b happens before the local variables are destroyed.
In my understanding, for C++17 and newer, what is returned to g() is a prvalue and it is not an object. As a result, if the destruction of the local variables happens after the prvalue of g() is created but before the copying (as in example 1), we would be copying memory that has been destroyed. So, I think of the outputs as "the compiler is waiting for the copying to be done before it destroys the local variable".
However, this is just a really vague understanding of mine. May I ask what actually happens behind the scenes so that such "waiting" can always happen properly?
Example 3: (just for demonstration)
void fun() {
A a;
B b;
throw b;
}
int main()
try
{
fun();
}
catch (B cb) { }
here the outputs are as follows:
A created
B created
B copied
B copied
B destroyed
A destroyed
B destroyed
B destroyed
In this situation, I think b is used to copy initialize a temporary, this temporary is then used to copy initialize cb. The local variables are destroyed after all those copying even though the temporary is an actually object, not prvalue. So, this situation is also different from example 1.
I am using Visual Studio 2022, C++20.
Function local variables are destroyed immediately after the function's return value is initialized.
In the first example, that return value is just a reference, so its creation has no observable side-effect. After the function returns, b is then initialized using the returned reference, so you see the "B copied" output after f has returned.
In the second example, the function's return value is a B object. Specifically the variable b in main due to the way prvalues work. That means that b is initialized directly by g, so you see the "B copied" output before g has returned.
In the third example, an exception is involved, and exceptions are complicated, and their specific mechanisms aren't terribly standardized. You can be sure that fun's local variables will be destroyed before the catch block is entered, but beyond that it's possible that the exception object could be copied any number of times during the stack unwinding.
Related
If class A contains class B, then when A destruct, B's destructor will be called first, i.e., the reversed order of their nested relationship.
But what if A contains a shared_ptr of B, while B contains a raw pointer to A, how should we handle the destructor to make it safe?
Considering the following example:
#include <iostream>
#include <memory>
#include <unistd.h>
struct B;
struct A {
int i = 1;
std::shared_ptr<B> b;
A() : b(std::make_shared<B>(this)) {}
~A() {
b = nullptr;
std::cout << "A destruct done" << std::endl;
}
};
struct B {
A *a;
B(A *aa) : a(aa) {}
~B() {
usleep(2000000);
std::cout << "value in A: " << a->i << std::endl;
std::cout << "B destruct done" << std::endl;
}
};
int main() {
std::cout << "Hello, World!" << std::endl;
{
A a;
}
std::cout << "done\n";
return 0;
}
You can see in A's destructor, I explicitly set b to nullptr, which will trigger B's destructor immediately, and blocking until it finish.
The output will be:
Hello, World!
value in A: 1
B destruct done
A destruct done
done
but if I comment out that line
~A() {
// b = nullptr; // <---
std::cout << "A destruct done" << std::endl;
}
The output will be:
Hello, World!
A destruct done
value in A: 1
B destruct done
done
it seems that A's destructor finished without waiting B to destruct. But in this case, I expected segment fault, since when A already destructed, B tried to access the member of A, which is invalid. But why the program doesn't produce segment fault? Does it happen to be OK (i.e., undefined behavior)?
Also, when I change
{
A a;
}
to
A * a = new A();
delete a;
the output is still the same, no segment fault.
It is important to be precise about what is happening. When A is destroyed, the following events happen in the following order:
A::~A() is called.
The A object's lifetime ends. The object still exists, but is no longer within its lifetime. ([basic.life]/1.3)
The body of A::~A() is executed.
A::~A() implicitly calls the destructors of direct non-static members of A in reverse declaration order ([class.dtor]/9, [class.base.init]/13.3)
A::~A() returns.
The A object ceases to exist ([class.dtor]/16). The memory that it used to occupy becomes "allocated storage" ([basic.life]/6) until it is deallocated.
(All references are to the C++17 standard).
In the second version of the destructor:
~A() {
std::cout << "A destruct done" << std::endl;
}
after the statement is printed, the member b is destroyed, which causes the owned B object to be destroyed. At that point, i has not yet been destroyed, so it is safe to access it. After that, the B destructor returns. Then, i is "destroyed" (see CWG 2256 for some subtleties). Finally, the destructor of A returns. At that point, it would no longer be legal to attempt to access the member i.
B has a pointer to A, but doesn't deallocate the memory of it (e.g. no delete). So The pointer is removed but not the memory allocated, which is all fine.
Basically, the pointer is on the stack and it contains the address of some (assumed) allocated memory on the heap. Yes it get removed from the stack, but the allocated memory remains. That's what delete is for. To remove the allocated memory on the heap. However in your case, you don't want that memory to be removed and your pointer is what we call a non-owning pointer. It points to something, but it's not responsible about the cleanup (actually B doesn't own the memory that the pointer is pointing to).
Just wanted to point out that your comment is incorrect:
~A() {
std::cout << "A destruct done" << std::endl;
}
A destructor is DONE when you get out of the curly braces. You can see that in a debugger, doing step-by-step. That is where b will be deleted.
If class A contains class B, then when A destruct, B's destructor will be called first, i.e., the reversed order of their nested relationship.
No. If you destroy an object of type A, that calls the destructor of A, so that is called first.
However, call to B's destructor will finish first: The destructor of A begins by executing the destructor body, and then proceeds to destroy the sub objects. The destructor body finishes first, then the sub object destructors, and finally the destructor of A will be complete.
But what if A contains a shared_ptr of B, while B contains a raw pointer to A, how should we handle the destructor to make it safe?
In the destructor body of A, make the pointed B point somewhere else other than the object that is being destroyed:
~A() {
b->a = nullptr;
}
If you point it to null such as in my shown example, then you must also make sure that B can handle the situation that B::a may be null i.e. check before accessing through the pointer.
it seems that A's destructor finished without waiting B to destruct.
That's not what we observe. The body of the destructor of A has finished, but the destructor does not finish until the member destructors finish first.
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.
Updated below: In clang, using an lvalue of a polymorphic object through its name does not activate virtual dispatch, but it does through its address.
For the following base class B and derived D, virtual function something, union Space
#include <iostream>
using namespace std;
struct B {
void *address() { return this; }
virtual ~B() { cout << "~B at " << address() << endl; }
virtual void something() { cout << "B::something"; }
};
struct D: B {
~D() { cout << "~D at " << address() << endl; }
void something() override { cout << "D::something"; }
};
union Space {
B b;
Space(): b() {}
~Space() { b.~B(); }
};
If you have a value s of Space, in Clang++: (update: incorrectly claimed g++ had the same behavior)
If you do s.b.something(), B::something() will be called, not doing the dynamic binding on s.b, however, if you call (&s.b)->something() will do the dynamic binding to what b really contains (either a B or D).
The completion code is this:
union SpaceV2 {
B b;
SpaceV2(): b() {}
~SpaceV2() { (&b)->~B(); }
};
static_assert(sizeof(D) == sizeof(B), "");
static_assert(alignof(D) == alignof(B), "");
#include <new>
int main(int argc, const char *argv[]) {
{
Space s;
cout << "Destroying the old B: ";
s.b.~B();
new(&s.b) D;
cout << "\"D::something\" expected, but \"";
s.b.something();
cout << "\" happened\n";
auto &br = s.b;
cout << "\"D::something\" expected, and \"";
br.something();
cout << "\" happened\n";
cout << "Destruction of D expected:\n";
}
cout << "But did not happen!\n";
SpaceV2 sv2;
new(&sv2.b) D;
cout << "Destruction of D expected again:\n";
return 0;
}
When compile with -O2 optimization and I run the program, this is the output:
$./a.out
Destroying the old B: ~B at 0x7fff4f890628
"D::something" expected, but "B::something" happened
"D::something" expected, and "D::something" happened
Destruction of D expected:
~B at 0x7fff4f890628
But did not happen!
Destruction of D expected again:
~D at 0x7fff4f890608
~B at 0x7fff4f890608
What surprises me is that setting the dynamic type of s.b using placement new leads to a difference calling something on the very same l-value through its name or through its address. The first question is essential, but I have not been able to find an answer:
Is doing placement new to a derived class, like new(&s.b) D undefined behavior according to the C++ standard?
If it is not undefined behavior, is this choice of not activating virtual dispatch through the l-value of the named member something specified in the standard or a choice in G++, Clang?
Thanks, my first question in S.O. ever.
UPDATE
The answer and the comment that refers to the standard are accurate: According to the standard, s.b will forever refer to an object of exact type B, the memory is allowed to change type, but then any use of that memory through s.b is "undefined behavior", that is, prohibited, or that the compiler can translate however it pleases. If Space was just a buffer of chars, it would be valid to in-place construct, destruct, change the type. Did exactly that in the code that led to this question and it works with standards-compliance AFAIK.
Thanks.
The expression new(&s.b) D; re-uses the storage named s.b and formerly occupied by a B for for storage of a new D.
However you then write s.b.something(); . This causes undefined behaviour because s.b denotes a B but the actual object stored in that location is a D. See C++14 [basic.life]/7:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
[...]
The last bullet point is not satisfied because the new type differs.
(There are other potential issues later in the code too but since undefined behaviour is caused here, they're moot; you'd need to have a major design change to avoid this problem).
Say there is an object A which owns an object B via std::unique_ptr<B>. Further B holds a raw pointer(weak) reference to A. Then the destructor of A will invoke the destructor of B, since it owns it.
What will be a safe way to access A in the destructor of B? (since we may also be in the destructor of A).
A safe way me be to explicitly reset the strong reference to B in the destructor of A, so that B is destroyed in a predictable manner, but what's the general best practice?
I'm no language lawyer but I think it is OK. You are treading on dangerous ground and perhaps should rethink your design but if you are careful I think you can just rely on the fact that members are destructed in the reverse order they were declared.
So this is OK
#include <iostream>
struct Noisy {
int i;
~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};
struct A;
struct B {
A* parent;
~B();
B(A& a) : parent(&a) {}
};
struct A {
Noisy n1 = {1};
B b;
Noisy n2 = {2};
A() : b(*this) {}
};
B::~B() { std::cout << "B dies. parent->n1.i=" << parent->n1.i << "\n"; }
int main() {
A a;
}
Live demo.
since the members of A are destructed in order n2 then b then n1. But this is not OK
#include <iostream>
struct Noisy {
int i;
~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};
struct A;
struct B {
A* parent;
~B();
B(A& a) : parent(&a) {}
};
struct A {
Noisy n1 = {1};
B b;
Noisy n2 = {2};
A() : b(*this) {}
};
B::~B() { std::cout << "B dies. parent->n2.i=" << parent->n2.i << "\n"; }
int main() {
A a;
}
Live demo.
since n2 has already been destroyed by the time B tries to use it.
What will be a safe way to access A in the destructor of B? (since we may also be in the destructor of A).
There isn't safe way:
3.8/1
[...]The lifetime of an object of type T ends when:
— if T is a class type with a non-trivial destructor (12.4), the destructor call starts [...]
I think it's straightforward that you can't access object after it's lifetime has ended.
EDIT: As Chris Drew wrote in comment you can use object after it's destructor started, sorry, my mistake I missed out one important sentence in the standard:
3.8/5
Before the lifetime of an object has started but after the storage which the object will occupy has been
allocated or, after the lifetime of an object has ended and before the storage which the object occupied is
reused or released, any pointer that refers to the storage location where the object will be or was located
may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise,
such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type void*,
is well-defined. Such a pointer may be dereferenced but the resulting lvalue may only be used in limited
ways, as described below. The program has undefined behavior if:
[...]
In 12.7 there is list of things you can do during construction and destruction, some of the most important:
12.7/3:
To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference)
to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or
indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or
access the value of) a direct non-static member of an object obj, the construction of obj shall have started
and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing
the member value) results in undefined behavior.
12.7/4
Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).
When a virtual function is called directly or indirectly from a constructor or from a destructor, including
during the construction or destruction of the class’s non-static data members, and the object to which the
call applies is the object (call it x) under construction or destruction, the function called is the final overrider
in the constructor’s or destructor’s class and not one overriding it in a more-derived class. If the virtual
function call uses an explicit class member access (5.2.5) and the object expression refers to the complete
object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the
behavior is undefined.
As has already been mentioned there is no "safe way". In fact as has been pointed out by PcAF the lifetime of A has already ended by the time you reach B's destructor.
I also just want to point out that this is actually a good thing! There has to be a strict order in which objects get destroyed.
Now what you should do is tell B beforehand that A is about to get destructed.
It is as simple as
void ~A( void ) {
b->detach_from_me_i_am_about_to_get_destructed( this );
}
Passing the this pointer might be necessary or not depending on the design ob B (If B holds many references, it might need to know which one to detach. If it only holds one, the this pointer is superfluous).
Just make sure that appropriate member functions are private, so that the interface only can be used in the intended way.
Remark:
This is a simple light-weight solution that is fine if you yourself completely control the communication between A and B. Do not under any circumstances design this to be a network protocol! That will require a lot more safety fences.
Consider this:
struct b
{
b()
{
cout << "b()" << endl;
}
~b()
{
cout << "~b()" << endl;
}
};
struct a
{
b ob;
a()
{
cout << "a()" << endl;
}
~a()
{
cout << "~a()" << endl;
}
};
int main()
{
a oa;
}
//Output:
b()
a()
~a()
~b()
"Then the destructor of A will invoke the destructor of B, since it owns it." This is not the correct way of invocation of destructors in case of composite objects. If you see above example then, first a gets destroyed and then b gets destroyed. a's destructor won't invoke b's destructor so that the control would return back to a's destructor.
"What will be a safe way to access A in the destructor of B?". As per above example a is already destroyed therefore a cannot be accessed in b's destructor.
"since we may also be in the destructor of A).". This is not correct. Again, when the control goes out of a's destructor then only control enters b's destructor.
Destructor is a member-function of a class T. Once the control goes out of destructor, the class T cannot be accessed. All the data-members of class T can be accessed in class T's constructors and destructor as per above example.
If you look only on the relations of the two classes A and B, the construction is well:
class A {
B son;
A(): B(this) {}
};
class B {
A* parent;
B(A* myparent): parent(myparent) {}
~B() {
// do not use parent->... because parent's lifetime may be over
parent = NULL; // always safe
}
}
The problems arise, if objects of A and B are proliferated to other program units. Then you should use the tools from std::memory like std::shared_ptr or std:weak_ptr.
I read the book C++ How to Program 8th Edition by Paul Deitel. There is a statement at p.645:
When an exception is thrown from the constructor for an object that's created in a new expression, the dynamically allocated memory for that object is released.
To verify this statement, I wrote code as follows:
#include <iostream>
#include <exception>
#include <memory>
class A{
public:
A(){std::cout << "A is coming." << std::endl;}
~A(){std::cout << "A is leaving." << std::endl;}
};
class B
{
public:
B()
{
std::cout << "B is coming." << std::endl;
A b;
throw 3;
}
~B(){std::cout << "B is leaving." << std::endl;}
};
int main(void)
{
try
{
std::shared_ptr<B> pi(new B);
}
catch(...)
{
std::cout << "Exception handled!" << std::endl;
}
}
The output is:
B is coming.
A is coming.
A is leaving.
Exception handled!
This shows that B's destructor isn't invoked, which seems to conflict with the statement above.
Is my code correct to verify the statement? If not, how should I modify it? If yes, does it mean that the statement is wrong?
You're confusing two things:
memory being released
the destructor being called
You've shown that the latter doesn't occur, which makes sense: how can you destroy something that wasn't properly constructed? Note that the member variables will have their destructors invoked, though, because by the time the constructor threw an exception all the member variables had been fully constructed.
But that has nothing to do with memory being released, which will assuredly occur.
[C++11: 15.2/2]: An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.
It means that everything in the ctor of B up to the point of the exception is destruction. An instance of B itself was never constructed, therefore it must not be destructed. Note also that pi was never constructed.
std::shared_ptr<B> pi(new B) - start with new B
new B - triggers the ctor of B
std::cout ... - the output
A b; - construct an A
throw 3; - calls ~A()
- rewind, new B is "aborted"
- std::shared_ptr<B> pi(new B) is "aborted"
You could modify your code to see, that the constructor of the std::shared_ptr is never hit by replacing it with a new class of yours, taking a pointer:
struct T {
T(B*) { std::cout << "T::T()\n"; }
};
...
try
{
T pi(new B); // instead of std::shared_ptr<B> pi(new B);
}
...
The constructor of T will not be hit (cf. "pi was never constructed").
Now assume that the constructor of B would allocate memory as in:
B()
{
A* a = new A(); // in contrast to A a;
throw 3;
}
were previously A::~A() was called, that is a was deconstructed, we now have a pointer, and pointers don't need to be deconstructed. However the memory allocated and assigned to a is not deleted. (Had you used a smart pointer std::unique_ptr<A> a = std::make_unique<A>();, the memory would have been released, because the destructor of std::unique_ptr<A> is called and it will release the memory.)