Cannot call C ++ destructor, depending on binding order

I ran into this problem in my application after checking it for memory leaks and found that some of my classes are not destroyed at all.

The code below is divided into 3 files, it is assumed that it implements the pimpl template. The expected scenario is for both the Cimpl constructor and the destructor to print their messages. However, this is not what I get with g ++. In my application, only the constructor is called.

classes.h:

 #include <memory> class Cimpl; class Cpimpl { std::auto_ptr<Cimpl> impl; public: Cpimpl(); }; 

classes.cpp:

 #include "classes.h" #include <stdio.h> class Cimpl { public: Cimpl() { printf("Cimpl::Cimpl()\n"); } ~Cimpl() { printf("Cimpl::~Cimpl()\n"); } }; Cpimpl::Cpimpl() { this->impl.reset(new Cimpl); } 

main.cpp:

 #include "classes.h" int main() { Cpimpl c; return 0; } 

Here is what I could discover next:

 g++ -Wall -c main.cpp g++ -Wall -c classes.cpp g++ -Wall main.o classes.o -o app_bug g++ -Wall classes.o main.o -o app_ok 

It seems that the destructor is called in one of two possible cases, and this depends on the binding order. With app_ok, I managed to get the correct script, while app_bug behaved exactly like my application.

Is there any kind of wisdom that I am missing in this situation? Thanks for any suggestion in advance!

+3
c ++ gcc destructor
Sep 07
source share
4 answers

The purpose of the pimpl idiom is to not expose the implementation class in the header file. But all standard smart pointers require a definition of their template parameter for visibility at the point of declaration for proper operation.

This means that this is one of the rare cases when you really want to use new , delete and a bare pointer. (If I am wrong about this and there is a standard smart pointer that can be used for pimpl, someone, please let me know.)

classes.h

 struct Cimpl; struct Cpimpl { Cpimpl(); ~Cpimpl(); // other public methods here private: Cimpl *ptr; // Cpimpl must be uncopyable or else make these copy the Cimpl Cpimpl(const Cpimpl&); Cpimpl& operator=(const Cpimpl&); }; 

classes.cpp

 #include <stdio.h> struct Cimpl { Cimpl() { puts("Cimpl::Cimpl()"); } ~Cimpl() { puts("Cimpl::~Cimpl()"); } // etc }; Cpimpl::Cpimpl() : ptr(new Cimpl) {} Cpimpl::~Cpimpl() { delete ptr; } // etc 
+1
Sep 07 '12 at 18:46
source share

The problem is that at the definition point of the auto_ptr<Cimpl> Cimpl is an incomplete type, that is, the compiler only saw the forward Cimpl . This is good, but since it ultimately deletes the object to which it contains a pointer, you must fulfill this requirement from [expr.delete] / 5:

If the object to be deleted has an incomplete class type at the deletion point, and the full class has a nontrivial destructor or deallocation function, the behavior is undefined.

So, this code works in undefined, and all bets are disabled.

+1
Sep 07 '12 at 18:52
source share

The code violates the rule of one definition. There is a definition of the Cimpl class in classes.h and another definition of the Cimpl class in the Cimpl file. The result is undefined behavior. It's ok to have more than one class definition, but they must be the same.

0
Sep 07 '12 at 18:14
source share

Edited for clarity, the original is saved below.

This code has undefined behavior, because in the context of main.cpp implicit destructor Cpimpl::~Cpimpl has only a forward Cimpl , but auto_ptr (or any other form of delete )) requires a full definition for legal clearance of Cimpl . Given that undefined behavior does not require further explanation of your observations.

Original answer:

I suspect that what is happening here is that the implicit Cpimpl destructor Cpimpl generated in the context of classes.h and does not have access to the full Cimpl definition. Then, when auto_ptr tries to complete its task and clear its containing pointer, it removes the incomplete class, which is undefined. Given that this is undefined, we no longer need to explain that it is perfectly acceptable for him to work differently depending on the order of the links.

I suspect that an explicit destructor for Cpimpl with a definition in the source file will solve your problem.

EDIT: Actually, now when I look at this again, I believe that your program violates one definition rule in the form in which it is located. In main.cpp he sees an implicit destructor that does not know how to call the Cimpl destructor (because it has only a forward declaration). In classes.cpp an implicit destructor has access to the Cimpl definition and, therefore, how to call its destructor.

0
Sep 07 '12 at 18:14
source share



All Articles