I've previously worked in a setting where exceptions have been turned off and failed memory allocation means that we kill the program. Now working with exceptions I'm wondering about the precise semantics of the following:
class Foo {
std::unique_ptr<Bar> x;
std::unique_ptr<Bar> y;
public:
Foo(): x{new Bar}, y{new Bar} {}
};
My question is what happens when new Bar throws when y is being allocated? I would assume that the destructor of x is called so that the first allocation is cleaned up. How is the language guaranteeing this? Anyone know a quote from the standard that explains the precise semantics?
Yes, all completely-constructed members will be destroyed. Your object will not be left in any sort of "half-alive" state. No memory leaks will occur.
[except.ctor]/3: If the initialization or destruction of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object's direct subobjects and, for a complete object, virtual base class subobjects, whose initialization has completed ([dcl.init]) [..] The subobjects are destroyed in the reverse order of the completion of their construction. [..]
We can demonstrate this ourselves:
#include <memory>
#include <iostream>
struct Bar
{
Bar(const char* name, bool doThrow = false) : m_name(name)
{
if (doThrow)
{
std::cout << name << ": Bar() throwing\n";
throw 0;
}
std::cout << name << ": Bar()\n";
}
~Bar() { std::cout << m_name << ": ~Bar()\n"; }
private:
const char* m_name;
};
class Foo {
std::unique_ptr<Bar> x;
std::unique_ptr<Bar> y;
public:
Foo(): x{new Bar("A")}, y{new Bar("B", true)} {}
};
int main()
{
try
{
Foo f;
}
catch (...) {}
}
// g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
// A: Bar()
// B: Bar() throwing
// A: ~Bar()
(live demo)
This is, in fact, one of the major benefits of so-called "smart pointers": exception safety. Were x a raw pointer, you'd have leaked the thing it pointed to, because a raw pointer's destruction doesn't do anything. With exception safety you can have RAII; without it, good luck.
If you are worried about the two new Bar expressions interleaving and throwing before the handles are initialized to hold what they are meant, the standard doesn't allow it.
First in [intro.execution]
12 A full-expression is
an init-declarator or a mem-initializer, including the constituent expressions of the initializer,
16 Every value computation and side effect associated with a
full-expression is sequenced before every value computation and side
effect associated with the next full-expression to be evaluated.
Without going too much into details, x{new Bar} and y{new Bar} in their entirety are both considered what standardese deems a "full-expression" (even though they are not expressions grammar-wise). The two paragraphs I quoted indicate that either the entire initialization of x (which includes new Bar) has to happen first, or the entire initialization of y has to happen first. We know from [class.base.init]
13.3 - Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order
of the mem-initializers).
So x is initialized in full, and then y. So even if new Bar throws while initializing y, x already owns the resource it's meant to hold. In which case, when the exception is thrown, the verbiage in [except.ctor] parageph 3 will apply to the fully constructed x, and it will be destructed, thus releasing the resource.
If an exception is thrown during object construction the object was not constructed and you shouldn't make any assumptions about the state of any members. The object is simply not usable. Any members that were initialized prior to the exception being thrown will be destructed in reverse order of their construction.
https://eel.is/c++draft/basic.life#def:lifetime describes lifetime rules.
Related
I have this example:
struct A{
A(){std::cout << "A's def-ctor\n";}
~A(){std::cout << "A's dtor\n";}
A(A const&){std::cout << "A's copy-ctor\n";}
A& operator = (A const&){std::cout << "A's copy-assign op\n"; return *this; }
};
struct Foo{
Foo() : curMem_(INT), i_(0){}
~Foo(){
if(curMem_ == CLS_A) // If I comment out this line then what happens?
a_.~A();
}
enum {INT, CHAR, CLS_A, BOOL} curMem_;
union{
int i_;
char c_;
A a_;
bool b_;
};
};
Foo f;
f.curMem_ = Foo::CLS_A;
f.a_ = A();
f.curMem_ = Foo::BOOL;
f.b_ = true;
We know that a class default destructor doesn't know which member of a class's member of a union type is active that is why we do need to define out version of destructor. So union's member data of class type are not automatically destroyed. So What will happen if I don't explicitly call the destructor of those class type member of the union?
If I comment the line in Foo destructor or remove the destructor itself what will happen? Is it undefined behavior?
My class A doesn't manage a resource via a raw pointer then why I bother to explicitly call its destructor when an of object of it is a member of a union? Thank you!
P.S: I have this from C++ primer 5th edition Chapter 19.6 unions:
Our destructor checks whether the object being destroyed holds a string. If so, the destructor explicitly calls the string destructor (§ 19.1.2, p. 824) to free the memory used by that string. The destructor has no work to do if the union holds a member of any of the built-in types.
"The destructor has no work to do if the union holds a member of any of the built-in types." I think he could add: "or of a class type which depends on the trivial destructor". What do you think?
The exact wording of the standard given in [basic.life]p6 is:
For an object of a class type, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression ([expr.delete]) is not used to release the storage, the destructor is not implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
(emphasis mine)
"depends on the side effects" seems pretty vague, and there are plenty of questions on stack overflow discussing this wording. Your A class's destructor seems to have the side effect of calling an I/O function, so it seems like you run into undefined behaviour.
Even if it wasn't UB, if it was a std::vector, std::string or std::fstream, you would leak resources like memory or file handles. It depends entirely on what the destructor of the class (and any members of that class) do.
Since "My class A doesn't manage a resource via a raw pointer", it should really have a trivial destructor. In which case, this point is moot and it is fine to not call the destructor.
I've always assumed that an object begins and ends its lifetime in the same memory location, but I've recently come across a scenario where I need to be sure. Specifically, I'm looking for a guarantee from the standard that no matter what optimizations the compiler performs the address an object is constructed at is the same one that it will have its destructor called from... and that its destructor is, indeed, guaranteed to be called from that location unless the program is terminating.
I've always taken this stuff for granted, but upon closer examination I can't find a guarantee, and there's some language around copy and move elision that I'm not sure how to interpret. I'm hoping that some of the more standards-conversant people here can point me to chapter and verse.
What you are looking for is defined in [intro.object]/1
[...] An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]).
This means the address cannot change as long as you can access it.
Specifically, I'm looking for a guarantee from the standard that no matter what optimizations the compiler performs the address an object is constructed at is the same one that it will have its destructor called from...
and that its destructor is, indeed, guaranteed to be called from that location unless the program is terminating.
The standard guarantees both for automatic variables and static variables as long as one doesn't do bad things with the objects. However, it does not guarantee either for objects allocated from the free store.
Even for automatic variables, a crafty programmer can subvert the intention through pointer manipulation and explicitly calling the destructor through a pointer.
In addition, the wrong destructor will be called when delete-ing a base class pointer when the base class does not have a virtual destructor. This will be a programming error, not the result of intention to subvert.
Example:
struct Base
{
int b;
};
struct Derived : virtual Base
{
float d;
};
int main()
{
{
Derived d1; // Not a problem.
}
{
Derived d1;
Derived* ptr = &d1;
delete ptr; // Bad. The programmer subverts the program.
// Must not use delete.
}
{
Derived* d2 = new Derived; // The destructor does not get called automatically.
}
{
Derived* d2 = new Derived;
delete d2; // OK. The proper destructor gets called.
}
{
Derived* d2 = new Derived;
Base* ptr = d2;
delete ptr; // Programmer error. The wrong destructor gets called.
}
}
As mentioned by Nathan Oliver, the standard states that:
[...] An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]).
Compilers respect this, and there are objects (similar to the one you describe) for which it must hold true. Consider std::mutex. A mutex cannot be copied or moved, and the reason for this is that it must remain at the same location in memory for the duration of it's lifetime in order to work.
So how does copy/move elision work?
Copy/move elision works by creating the object where it needs to go. It's that simple.
We can see this behavior for ourselves:
#include <iostream>
struct Foo {
Foo() {
std::cout << "I am at " << (void*)this << '\n';
}
// Delete copy and move, to ensure it cannot be moved
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
};
Foo getFoo() {
return Foo();
}
int main() {
Foo* ptr = new Foo(getFoo());
std::cout << "Foo ptr is at " << (void*)ptr << '\n';
delete ptr;
}
This code outputs:
I am at 0x201ee70
Foo ptr is at 0x201ee70
And we see that Foo remains at the same location for the duration of it's lifetime, without ever being copied or moved, even though it's being created in dynamically allocated memory.
How does the compiler know where to create an object?
If a function returns a type that is not trivially copyable, then that function takes an implicit parameter representing the memory address where it's supposed to construct the return value.
Suppose I have a class that may run some code asynchronously, and that asynchronous code uses that class instance to do things like call member functions, read data members, etc. Obviously the class instance must outlive the background thread in order for those accesses to be safe. It is sufficient to ensure this by joining the background thread in the destructor? For example:
#include <iostream>
#include <thread>
class foo final
{
public:
foo() = default;
void bar() {
std::cout << "Hopefully there's nothing wrong with using " << this << "\n";
}
void bar_async() {
if (!m_thread.joinable()) {
m_thread = std::thread{&foo::bar, this};
}
}
~foo() {
if (m_thread.joinable()) {
std::cout << "Waiting for " << m_thread.get_id() << "\n";
m_thread.join();
}
}
private:
std::thread m_thread;
};
int main() {
foo f;
f.bar_async();
}
Specifically, I'm worried about object lifetime rules:
For any object of class types whose destructor is not trivial, lifetime ends when the execution of the destructor begins.
... after the lifetime of an object has ended and before the storage which the object occupied is reused or released, the following uses of the glvalue expression that identifies that object are undefined: ...
Access to a non-static data member or a call to a non-static member function.
But to me, a strict reading of the above would also imply that calling this->bar() from inside ~foo() directly is undefined, which is "obviously" not the case.
cppreference is right but it is talking about accessing the members from the object, not from inside the destructor. If we look at [class.cdtor]/1 we see that
For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.
emphasis mine
So, as long as we are in the destructor we can still work with the member objects as those are not destroyed until the scope of the destructor ends.
So, calling join on the thread is fine here. If you think about it if it wasn't then things like lock guards would be useless if accessing the mutex they refer to was undefined behavior.
My intuition is no. This is because thread::join can throw an exception and you don't want exceptions escaping your destructor. It may be okay if you wrap it in a try catch and handle the exception properly though.
#include <iostream>
using namespace std;
struct A
{
A() { cout << "A" << endl; }
~A() { cout << "~A" << endl; }
};
A Ok() { return {}; }
A NotOk() { throw "NotOk"; }
struct B
{
A a1;
A a2;
};
void f(B) {}
int main()
{
try
{
f({ Ok(), NotOk() });
}
catch (...)
{}
}
vc++ and clang output:
A
~A
While gcc outputs:
A
It seems a serious bug of GCC.
For reference, see GCC bug 66139 and "A serious bug in GCC" by Andrzej Krzemieński.
I just wonder:
Does the C++ standard guarantee that uniform initialization is exception-safe?
It seems so:
Curiously found in §6.6/2 Jump Statements [stmt.jump] of all places (N4618):
On exit from a scope (however accomplished), objects with automatic
storage duration (3.7.3) that have been constructed in that scope are
destroyed in the reverse order of their construction. [ Note: For
temporaries, see 12.2. —end note ] Transfer out of a loop, out of a
block, or back past an initialized variable with automatic storage
duration involves the destruction of objects with automatic storage
duration that are in scope at the point transferred from but not at
the point transferred to. (See 6.7 for transfers into blocks). [ Note:
However, the program can be terminated (by calling std::exit() or
std::abort() (18.5), for example) without destroying class objects
with automatic storage duration. —end note ]
I think the emphasis here is on the "(however accomplished)" part. This includes an exception (but excludes things that cause a std::terminate).
EDIT
I think a better reference is §15.2/3 Constructors and destructors [except.ctor] (emphasis mine):
If the initialization or destruction of an object other than by
delegating constructor is terminated by an exception, the destructor
is invoked for each of the object’s direct subobjects and, for a
complete object, virtual base class subobjects, whose initialization
has completed (8.6) and whose destructor has not yet begun execution,
except that in the case of destruction, the variant members of a
union-like class are not destroyed. The subobjects are destroyed in
the reverse order of the completion of their construction. Such
destruction is sequenced before entering a handler of the
function-try-block of the constructor or destructor, if any.
This would include aggregate initialization (which I learned today can be called non-vacuous initialization)
...and for objects with constructors we can cite §12.6.2/12 [class.base.init](emphasis mine):
In a non-delegating constructor, the destructor for each potentially
constructed subobject of class type is potentially invoked (12.4). [
Note: This provision ensures that destructors can be called for
fully-constructed subobjects in case an exception is thrown (15.2).
—end note ]
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.