views:

746

answers:

6

The following code demonstrates a weird problem I have in a Turbo C++ Explorer project. One of the three stack objects in D::D() is not destroyed after going out of scope.

This only happens if compiled in release mode, the auto_ptrs a_ and b_ are of different types and the exception thrown doesn't inherit from std::exception. It appears to work just fine in VC++ 2005 and C++ Builder 2009. I did install the BDS2006 Update 2, the hotfix rollup and hotfix 12.

Is it my code or the compiler? Do you know of a fix? Not being able to reliably use auto_ptr in a VCL project would be quite inconvenient.


#include <memory>
#include <stdexcept>
#include <iostream>

typedef std::exception my_error; // will work fine if replaced with line below
//class my_error : public std::exception {};

class A {};
class B {};

class C
{
public:
    C(int id) : id_(id) { std::cout << "C::C() " << id_ << std::endl; };
    ~C() { std::cout << "C::~C() " << id_ << std::endl; };
private:
    int id_;
};

class D
{
public:
    D()
    {
        C c1(1);
        C c2(2);
        C c3(3);

        throw my_error();
    };

private:
    std::auto_ptr<A> a_;
    std::auto_ptr<B> b_; // will work fine if replaced with line below
//  std::auto_ptr<A> b_;
//  std::auto_ptr<C> c_; // see expected output
};

#pragma argsused
int main(int argc, char* argv[])
{
    try
    {
        D d;
    }
    catch (...)
    {
        std::cout << "caught exception" << std::endl;
    }

    return 0;
}


Expected:

C::C() 1
C::C() 2
C::C() 3
C::~C() 3
C::~C() 2
C::~C() 1
caught exception


Got:

C::C() 1
C::C() 2
C::C() 3
C::~C() 2
C::~C() 1
caught exception


Got (with line '// std::auto_ptr<C> c_;' uncommented):

C::C() 1
C::C() 2
C::C() 3
C::~C() 1
caught exception


Edit: Made suggested changes

Edit 2:
I just tested it with C++ Builder 2007 (11.0.2902.10471), which shows the same problem. The release configuration works as soon as I check the "Debug information" box in Project -> Options -> C++ Compiler -> Debugging. It surprises me that the executable gets smaller with "Debug information" enabled (down to 31.5 KB from 39.5 KB ).

Edit 3:
In Turbo C++ Explorer (C++ Builder 2006) (10.0.2288.42451) the release configuration works if I uncheck the "Inline function expansion (-vi)" box in Project -> Options -> C++ Compiler -> Debugging. Replacing the first line (#include <memory>) with the following code makes it work, too.

#pragma option push -vi-
#include <memory>
#pragma option pop
+1  A: 

If an exception is thrown in an object constructor, the destructor will not run.

The compiler has no way of knowing if the constructor completed sufficiently for the destructor to run correctly.

See http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.4

EDIT: Responding to the comment below... In this case, it's most likely a compiler bug lumping the 'don't run the destructor' rule in with incorrectly not destroying objects on the stack.

Andrew Rollings
In this sample, the questioner is worried abouth the destructor of locals in the constructor, not the destructor of D
JaredPar
Good point. Well made. In which case, it's most likely a compiler bug. I will update my answer accordingly.
Andrew Rollings
also, if the constructor or destructor has side effects, the compiler is not allowed to optimize the locals away. it looks like it does just that. maybe he should try printing out the address of the two objects (their this pointer). wouldn't be surprised if they happen to be on the same address.
Johannes Schaub - litb
+5  A: 

This appears to be a compiler bug. I just ran the same sample in VS2008SP1 and got the expected output.

JaredPar
+4  A: 

For whatever it's worth, GCC 3.4.6 does the expected thing:

$ g++ main.cpp

$ a.out
C::C()
C::C()
C::~C()
C::~C()
caught exception
Arkadiy
+1  A: 

Maybe the cout stream is not flushed? Can you try with cerr instead? or directly put a breakpoint in destructor and check if they are hit?

Ismael
Surely if the output stream wasn't being flushed, the 'caught exception' string wouldn't appear either.
Andrew Rollings
Also, he says it only happens in release mode... Makes it difficult to put a breakpoint on the destructor :)
Andrew Rollings
let's have a second reason why i don't believe the cause is buffering of cout: he uses std::endl, which in effect calls .flush() which will flush the stream after putting a newline
Johannes Schaub - litb
Well you can put a breakpoint directly in your code with DebugBreak(), and run the code even in release mode in your debugger. If you have a .pdb with symbols you will have your functions names.
Ismael
cerr usually is not buffered it should print immediately on screen, instead cout can be buffered and it await a flush() call to print on screen. (I was bitten for this behavior sometimes)
Ismael
A: 

Looks like a bug in the exception handling stack unwind code. Try making a simple class E with an instance of it in D's constructor and see if it gets called.

Rob K
+2  A: 

It's a compiler bug in C++Builder 2006. C++Builder 2009 fixes it; this is the output I get for BCC v6.1:

C::C() 1
C::C() 2
C::C() 3
C::~C() 3
C::~C() 2
C::~C() 1
caught exception
Moritz Beutel