I was reading subsection 18.2 Constructors and Destructors of section 18 exception handling in C++17 standard draft where there is an example(page 384). I tried to understand where destruction of object A returned inside the try block happens but couldn't find. So I copied the example, added some prints and see that dtor of that object is never called. What am I missing here? Someone please explain what's happening here.
#include <stdio.h>
struct A {
int a_;
explicit A(int a):a_(a) {
printf("A(%d)'s ctor\n", a_);
}
~A() {
printf("A(%d)'s dtor\n", a_);
}
};
struct Y { ~Y() noexcept(false) {
printf("y's dtor\n");
throw 0; } };
A f() {
try {
A a(23);
Y y;
A b(56);
return A(100); // #1 who destructs this ??
} catch (...) {
printf("handling exception..\n");
}
printf("At line %d now..\n", __LINE__);
return A(200); // #2
}
int main() {
auto ret = f();
printf("f returned A(%d) object\n", ret.a_);
return 0;
}
Above code outputs following:
A(23)'s ctor
A(56)'s ctor
A(100)'s ctor
A(56)'s dtor
y's dtor
A(23)'s dtor
handling exception..
At line 34 now..
A(200)'s ctor
f returned A(200) object
A(200)'s dtor
...Program finished with exit code 0
According to the C++17 standard [except.ctor]/2:
If an exception is thrown during the destruction of temporaries or local variables for a return statement, the destructor for the returned object (if any) is also invoked. The objects are destroyed in the reverse order of the completion of their construction.
Then there is an illustrative example (which you modified slightly) to demonstrate: after the Y destructor throws, the next thing that happens is that the A(100) object should be destroyed, so you should see a destruction message.
The posted output indicates a compiler bug. This is very similar to the issue reported as LLVM bug 12286 and gcc bug 33799.
The latter is marked as being fixed in GCC 10 (after initially being reported in 2007!). However, testing with the version 10.0.1 20200418 (experimental) on Godbolt: even though the test case in the 33799 bug report is fixed, the code in this question remains unfixed. I added a comment to that bug report about this example.
Generally, the caller of a function is responsible for destroying the function's return value, but the precise details are part of the platform C++ ABI.
On most ABIs, calling a function that returns a non-trivial value (anything that doesn't safely fit in a register or perhaps two), is done by passing a hidden extra argument specifying where the return value should be constructed. The caller allocates space in it's frame for the return value and passes a pointer to that space.
Related
The following example compiles with both gcc 11 on Linux (GNU STL) and clang 12 on FreeBSD (Clang STL). On Linux, it runs and prints values 1 and 2. On FreeBSD, it prints value 1 and then crashes with a SEGV. I don't quite understand the object lifetimes -- so the whole thing may be UB and the runtime behavior might not be relevant. I do know that the implementation of std::unique_ptr between those two STLs differs in an important way: Clang STL resets the internal pointer of a std::unique_ptr to nullptr at the start of the destructor, while GNU STL leaves the pointer alone.
#include <iostream>
#include <memory>
struct C {
struct Private {
C* m_owner;
int m_x;
Private(C* owner) : m_owner(owner), m_x(0) {}
~Private() { m_owner->cleanup(); }
void cleanup() { std::cout << "Private x=" << ++m_x << '\n'; }
};
std::unique_ptr<Private> d;
C() { d = std::make_unique<Private>(this); }
~C() = default;
void cleanup() { d->cleanup(); }
};
int main(int argc, char **argv)
{
C c;
c.cleanup(); // For display purposes, print 1
return 0; // Destructors called, print 2
}
Output on FreeBSD:
Private x=1
Segmentation fault (core dumped)
and a snippet of backtrace:
* thread #1, name = 'a.out', stop reason = signal SIGSEGV: invalid address (fault address: 0x8)
frame #0: 0x00000000002032b4 a.out`C::Private::cleanup() + 52
a.out`C::Private::cleanup:
-> 0x2032b4 <+52>: movl 0x8(%rax), %esi
My reason for thinking this might be UB is this:
at return 0, c's lifetime is ending.
the destructor ~C() runs. Once the body (defaulted) of the destructor is done, the lifetime of the object is over and using that object is UB.
now the destructors for sub-objects (member-objects?) of the object run.
the destructor ~std::unique_ptr<Private> runs. It runs the destructor for the held object.
the destructor ~Private() uses a pointer to a no-longer-alive object m_owner to call a member function.
I'd appreciate an answer that points out if this understanding of object lifetimes is correct.
If it's not UB, then there's a separate quality-of-implementation issue (or I should check the d-pointer before calling methods on it, but that seems a bit derpy for a pimpl; then we get if(d)d->cleanup() which is needed with one STL implementation and which is a useless check in another).
In the interest of posing a single question: does this code exhibit UB in the statement m_owner->cleanup() (line 9) during the destruction of object c ?
Yes, the lifetime of the object that m_owner refers to has already ended and it's destructor call completed when m_owner->cleanup(); is called. The call is therefore UB.
I expected to see copy elision from Named Return Value Optimization (NRVO) from this test program but its output is "Addresses do not match!" so NRVO didn't happen. Why is this?
// test.cpp
// Compile using:
// g++ -Wall -std=c++17 -o test test.cpp
#include <string>
#include <iostream>
void *addr = NULL;
class A
{
public:
int i;
int j;
#if 0
~A() {}
#endif
};
A fn()
{
A fn_a;
addr = &fn_a;
return fn_a;
}
int main()
{
A a = fn();
if (addr == &a)
std::cout << "Addresses match!\n";
else
std::cout << "Addresses do not match!\n";
}
Notes:
If a destructor is defined by enabling the #if above, then the NRVO does happen (and it also happens in some other cases such as defining a virtual method or adding a std::string member).
No methods have been defined so A is a POD struct, or in more recent terminology a trivial class. I don't see an explicit exclusion for this in the above links.
Adding compiler optimisation (to a more complicated example that doesn't just reduce to the empty program!) doesn't make any difference.
Looking at the assembly for a second example shows that this even happens when I would expect mandatory Return Value Optimization (RVO), so the NRVO above was not prevented by taking the address of fn_a in fn(). Clang, GCC, ICC and MSVC on x86-64 show the same behaviour suggesting this behaviour is intentional and not a bug in a specific compiler.
class A
{
public:
int i;
int j;
#if 0
~A() {}
#endif
};
A fn()
{
return A();
}
int main()
{
// Where NRVO occurs the call to fn() is preceded on x86-64 by a move
// to RDI, otherwise it is followed by a move from RAX.
A a = fn();
}
The language rule which allows this in case of returning a prvalue (the second example) is:
[class.temporary]
When an object of class type X is passed to or returned from a function, if X has at least one eligible copy or move constructor ([special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object.
The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
[Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers.
— end note
]
Why does Return Value Optimization not happen [in some cases]?
The motivation for the rule is explained in the note of the quoted rule. Essentially, RVO is sometimes less efficient than no RVO.
If a destructor is defined by enabling the #if above, then the RVO does happen (and it also happens in some other cases such as defining a virtual method or adding a std::string member).
In the second case, this is explained by the rule because creating the temporary is only allowed when the destructor is trivial.
In the NRVO case, I suppose this is up to the language implementation.
On many ABIs, if a return value is a trivially copyable object whose size/alignment is equal to or less than that of a pointer/register, then the ABI will not permit elision. The reason being that it is more efficient to just return the value via a register than via a stack memory address.
Note that when you get the address either of the object in the function or the returned object, the compiler will force the object onto the stack. But the actual passing of the object will be via a register.
I just upgrade from Visual Studio 2013 to 2015, and I'm running into a bunch of issues with things that used to work in 2013, but which do not in 2015.
For example, here's one that has me stumped. I created a test-case out of the original code.
Basically, the code runs some operations in a thread, via std::async(). Within the thread, exceptions might be thrown (A), which should be placed in the future object returned by std::async(). The weird thing is that in (B), the destructor of Ex is called, but the object is still thrown aftewards. In the try-block, when the ex (D) variable leaves the score, if 'mInts' vector (X) is a member, the program would crash. If I leave 'mInts' commented out, as below, I still get weird behavior. For example, this is what's printed with the code below: notice how the constructor is called one, but the destructor is called 4 times:
Output:
constructor
destructor
before exception
after exception
destructor
has exception
destructor
destructor
Code:
using FutureList = std::vector<std::future<void>>;
struct Ex {
Ex() {
std::cout << "constructor\n";
}
Ex(const Ex&) = delete;
Ex(Ex&&) {
std::cout << "move constructor";
}
~Ex() {
std::cout << "destructor\n";
}
void operator=(const Ex&) {
std::cout << "assign\n";
}
// std::vector<int> mInts; (X)
};
TEST(Explore, Test1) {
FutureList futures;
futures.push_back(
std::async(std::launch::async, []() {
throw Ex(); // (A)
}));
std::exception_ptr ex;
for (auto& i : futures) {
try {
i.get(); // (B)
std::cout << "Doesn't get here.\n";
}
catch (...) { // (C)
std::cout << "before exception\n";
ex = std::current_exception(); // (D)
std::cout << "after exception\n";
}
}
if (ex) {
std::cout << "has exception\n";
}
}
Actually, MSVC doesn't call the copy constructor in your example. There's no code to call; the function is deleted. It does something worse: it treats the class as being trivially copyable and does a memcpy on the object. That's the reason for the crash when you have a member of type std::vector<int>.
The problem is not directly related to std::async. It's caused by the implementation of std::exception_ptr. The shared state of a std::future uses an exception_ptr to store an exception, so using a future in the context of exceptions will trigger the problem. The issue can otherwise be reproduced when using only std::current_exception(), which involves a copy in VC 14's standard library implementation (the Standard allows that).
The problem is with the implementation of __ExceptionPtr::_CallCopyCtor in crt/src/stl/excptptr.cpp. It essentially does a test that goes like "Is there a copy constructor? No? Great, we can do a memcpy then!".
Another problem is that the test ignores access specifiers. If you provide a copy constructor but make it private, it will be called.
The test is done at runtime, so no chance for a compile-time error, and, unfortunately, that's the only way to do it: there is no general compile-time test that will tell what type of exception object an std::exception_ptr will point to in all cases.
The proposed resolution for Core issue 1863 aims to avoid this problem by requiring that
[...] the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible. [...]
The issue is in Ready status, which is one step away from adoption.
So, while this is definitely a problem with MSVC, it's also related to an open issue in the Standard. For now, it looks like a good idea to provide a copy constructor for exception objects, even if the Standard doesn't require it (yet).
Update: The resolution for issue 1863 has been adopted into the N4567 working draft, so compilers implementing the change are required to reject the code if a suitable constructor is not available.
It seems that MSVC 2015 still calls the copy constructor, even though it's marked deleted. To get around this issue, I defined the copy constructor.
The issue with the printout was because there was no printing in the copy constructor. I added some, and the constructor/destructor count matched.
Still, MSVC 2015 shouldn't be calling the copy-constructor if it's marked deleted. If it must be called, then it should issue an error.
class Error1
{
public:
int errorcode;
Error1(int x):errorcode(x){ cout<<"CTOR Error1"<<endl; }
//Error1(Error1& obj ){
// errorcode = obj.errorcode;
// cout<<"CopyCTOR Error1"<<endl;
//}
~Error1(){cout<<"DTOR Error1"<<endl; }
};
void fun()
{
cout<<"Inside fun"<<endl;
throw(Error1(5));
}
int main()
{
try{
fun();
}
catch(Error1& eobj)
{
cout<<"Error1 type occured with code:"<<eobj.errorcode<<endl;
}
cin.get();
}
OUTPUT:
Inside fun
CTOR Error1
DTOR Error1
Error1 type occured with code:5
DTOR Error1
This output indicates that a Error1 object is copy constructed for the catch handler. Since copy constructor is not defined for Error1 object default copy constructor is used.
When i uncomment the commented section for defining a copy constructor i get the the following output.
Inside fun
CTOR Error1
Error1 type occured with code:5
DTOR Error1
Why is it that only one DTOR is getting called? Even if exception is caught by reference i believe a temporary is still created.
What compiler are you using?
When you introduce (i.e. uncomment) your version of copy constructor with Error1& obj argument, the code is supposed to become invalid. throw is supposed to be able to create a copy of its argument, while your version of copy constructor disables copying of temporaries. The code is ill-formed. If your compiler accepts it, its is probably because it illegally allows binding non-const references to temporaries (I suspect it is MSVC++ compiler with extensions enabled).
The original experiment works as it is supposed/allowed to. The argument of throw is copied to an internal temporary which is later used to initialize catch parameters. Although the compilers are allowed to use your original temporary directly, extending its lifetime accordingly.
There may be other errors, but what I see right now is that throw(Error1(5)); creates a temporary (or rvalue) of type Error1. You want an lvalue, which means that you should either do throw(*new Error1(5)); (which I believe will create a memory leak, but I may be wrong), or you could create a global Error1 object and just throw that.
PS: I would be interested to know if throw(*new Error1(5)); does create a memory leak, if anyone would like to comment. Does catch destroy the object it catches? If so I think you should be fine with just creating new Error1s whenever you need them.
Why is the destructor not invoked in this code?
#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; throw; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};
int main()
{
boost::scoped_ptr<MyClass> myinst(new MyClass);
std::cout << myinst->increment() << '\n';
std::cout << myinst->increment() << '\n';
}
EDIT
From the answers, In understand that when an exception happens in the constructor, destructor will not be invoked. But if the exception happens in the main(), ie after the MyClass object is fully instantiated, will the MyClass destructor be invoked? If not, then why it is a smart pointer?
Adding the code
#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};
int main()
{
boost::scoped_ptr<MyClass> myinst(new MyClass);
throw 3;
std::cout << myinst->increment() << '\n';
std::cout << myinst->increment() << '\n';
}
Output:
MyClass Allocated
terminate called after throwing an instance of 'int'
Aborted
A C++ object's lifetime begins only after its constructor completes successfully.
Since the exception was thrown before constructor call was complete you don't have an complete object and hence no destructor.
Herb Sutter explains this nicely, to quote him:
Q: What does emitting an exception from a constructor mean?
A: It means that construction has failed, the object never existed, its lifetime never began. Indeed, the only way to report the failure of construction -- that is, the inability to correctly build a functioning object of the given type -- is to throw an exception. (Yes, there is a now-obsolete programming convention that said, "if you get into trouble just set a status flag to 'bad' and let the caller check it via an IsOK() function." I'll comment on that presently.)
In biological terms,
conception took place -- the constructor began -- but despite best efforts it was followed by a miscarriage -- the constructor never ran to term(ination).
Incidentally, this is why a destructor will never be called if the constructor didn't succeed -- there's nothing to destroy. "It cannot die, for it never lived." Note that this makes the phrase "an object whose constructor threw an exception" really an oxymoron. Such a thing is even less than an ex-object... it never lived, never was, never breathed its first. It is a non-object.
We might summarize the C++ constructor model as follows:
Either:
(a) The constructor returns normally by reaching its end or a return statement, and the object exists.
Or:
(b) The constructor exits by emitting an exception, and the object not only does not now exist, but never existed as an object.
EDIT 1:
But if the exception happens in the main(), ie after the MyClass object is fully instantiated, will the MyClass destructor be invoked?
Yes, it will be!
That is the purpose of using scoped_ptr, Once an exception is thrown in main, Stack Unwinding would cause all local objects to be deallocated, this means that myinst(which resides on stack) will also be deallocated, which in turn will call the destructor of MyClass.
Refer the Boost doccumentation when in doubt:
The scoped_ptr class template stores a pointer to a dynamically allocated object. (Dynamically allocated objects are allocated with the C++ new expression.) The object pointed to is guaranteed to be deleted, either on destruction of the scoped_ptr, or via an explicit reset
EDIT 2:
Why does your edited program crash?
Your program shows crashes because, You throw an exception but you never catch it. when such a scenario occurs an special function called terminate() is called whose default behavior is to call abort().It is implementation defined behavior whether stack is Unwound before terminate() is called in this particular scenarioRef 1.Seems your implementation doesn't & you should not rely on this behavior as well.
You can modify your program as follows to handle the exception and you should get the behavior you were expecting:
#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};
void doSomething()
{
boost::scoped_ptr<MyClass> myinst(new MyClass);
throw 3;
}
int main()
{
try
{
doSomething();
}
catch(int &obj)
{
std::cout<<"Exception Handled";
}
}
Ref1C++03 15.5.1 The terminate() function
In the following situations exception handling must be abandoned for less subtle error handling techniques:
....
— when the exception handling mechanism cannot find a handler for a thrown exception (15.3),
....
In such cases,
void terminate();
is called (18.6.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before terminate() is called. In all other situations, the stack shall not be unwound before terminate() is called. An implementation is not permitted to finish stack unwinding prematurely based on a determination that the unwind process will eventually cause a call to terminate().
Because calling the destructor doesn't make sense in this case.
You only destruct things which are constructed, yet your object never fully constructs. Your class members have been constructed, though, and will have their destructors called.
If a constructor throws exception, then the destructor of the class will not be called, because the object is not fully constructed.
See this link how to manage resources in such situation:
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10
When the exception is thrown from the constructor (beginning or half way or at the end of the call), then it's assured that the object is not constructed.
So it's well defined not to invoke the destructor of an object which was never constructed.
Here is one related FAQ from Bjarne's website.
The destructor for MyClass was never invoked because no objects of type MyClass were ever constructed. Each attempt to construct one was aborted, due to the exception being thrown.
As an aside, if you want your debug messages to display -- especially if you're dealing with the program crashing -- you really ought to flush the streams: i.e. using std::endl instead of '\n' at the end of line. (or inserting std::flush)
While merely using '\n' often works, there are enough situations where it fails and it's really, really confusing to debug if you don't make a habit of doing things right.