Will we get UB then?
I tried this:
#include <iostream>
struct B
{
B(){ std::cout << "B()" << std::endl; }
~B(){ std::cout << "~B()" << std::endl; }
};
struct A
{
B b;
A(){ std::cout << "A()" << std::endl; throw std::exception(); }
~A(){ std::cout << "~A()" << std::endl; }
};
int main()
{
A a;
}
The destructor was not called for both A
and B
.
The actual output:
B()
A()
terminate called after throwing an instance of 'std::exception'
what(): std::exception
bash: line 7: 21835 Aborted (core dumped) ./a.out
http://coliru.stacked-crooked.com/a/9658b14c73253700
So any time the constructor throws during initialization of block scope variables, do we get UB?
No, throwing an exception is the best way to signal an error during object construction. (Since there's no return value, there's no other way, other than constructing a headless object, which is bad style in C++.)
From the man himself, Bjarne Stroustrup: http://www.stroustrup.com/bs_faq2.html#ctor-exceptions
(If you are working in a project where exceptions aren't allowed, then you have to make the constructor infallible, and move any logic that could fail into a factory function that has the possibility of returning an error.)
Re: "But my destructor was not called"
Indeed. In C++ the lifetime of an object is said to begin when the constructor runs to completion. And it ends right when the destructor is called. If the ctor throws, then the dtor is not called.
(But dtors of any member variable objects, whose ctors already ran to completion before this ctor ran, are called.)
You should consult the standard, or a good textbook for more details, esp. related to what happens when inheritance is involved. As a general rule of thumb, destructors are called in the reverse order of construction.
Your question about why "~B" was not called in your specific code, it's because you do not catch the exception in main. If you change your code so that main catches the exception, then "~B()" will be called. But, when an exception is thrown which has no catch, the implementation is free to terminate the program without calling destructors or destroying statically initialized objects.
Reference in C++11 standard (emphasis mine):
15.5.1 The std::terminate() function [except.terminate]
1 In some situations exception handling must be abandoned for less subtle error handling techniques.
...
2 In such cases, std::terminate() is called (18.8.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called.
As a side note, generally speaking with gcc and clang, ~B
will be called anyways in your example program, while with MSVC, ~B
will not be called. Exception handling is complex and the standard allows that compiler writers can experiment with and choose what implementation that they think is best in this regard, but they cannot choose to give undefined behavior.
If it's really important for your program that the destructors are called even in this case, then you should make sure to catch exceptions in main
so that your code will be portable (work the same on all conforming compilers). For example:
int main() {
try {
A a;
} catch (...) {}
}
This way, compilers like MSVC will be obligated to call the destructor of B
before exiting.
Throwing exceptions in the constructor is standard way of the error handling and is not an undefined behavior. If you throw in constructor it is assumed that an object was not initialized properly, so its destructor is not called.
Here is an example to check destruction order, when it happens.
#include <iostream>
#include <stdexcept>
using namespace std;
struct KillMe {
const char* name;
KillMe(const char*n): name{n} {clog<<"Hello "<<name<<endl;}
~KillMe() {clog<<"Bye "<<name<<endl;}
};
struct CantLive : KillMe {
KillMe fool{"Fool"}, barf;
CantLive(): KillMe{"Dady"}, barf{"Barf"} {
clog<<"Dady cannot live..."<<endl;
throw logic_error("CantLive cannot live");
}
};
int main() {
try {CantLive catch_me;}
catch(...) {clog<<"Gotcha!"<<endl;}
clog<<"Now let's try without catcher..."<<endl;
CantLive dont_catch_me;
return 0;
}
See how constructions and destructions happen:
Hello Dady
Hello Fool
Hello Barf
Dady cannot live...
Bye Barf
Bye Fool
Bye Dady
Gotcha!
Now let's try without catcher...
Hello Dady
Hello Fool
Hello Barf
Dady cannot live...
terminate called after throwing an instance of 'std::logic_error'
what(): CantLive cannot live
exited, aborted
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With