2

I'm trying to use a boost::scoped_ptr with my implementation class that is visible only in the cpp file of the containing class. The containing class has an explicitly defined destructor (that isn't inline), but my compiler (Borland C++ 5.6.4) fails to compile.

If I use boost::shared_ptr instead, the same example compiles and runs as expected.

What am I doing wrong?


Edit: sorry for forgetting showing the source code, compiler error, and (expected) output here it is:

Source code

File check_shared.cpp:

// shortened.
#include "SmartPtrTest.h"
void check_shared()
{
    Containing t;
}

File SmartPtrTest.h:

#include <boost/noncopyable.hpp>
#include <boost/smart_ptr.hpp>

class Impl;
#define smart_ptr boost::scoped_ptr

class Containing: private boost::noncopyable
{
public:
    Containing();
    ~Containing();
private:
    smart_ptr<Impl> impl;
};

File SmartPtrTest.cpp:

#include "SmartPtrTest.h"
#include <iostream>

using namespace std;

class Impl {
public:
    Impl() {
        cout << "ctr Impl" << endl;
    }
    ~Impl() {
        cout << "dtr Impl" << endl;
    }
};

Containing::Containing(): impl(new Impl)
{
    cout << "ctr Containing" << endl;
}

Containing::~Containing()
{
    cout << "dtr Containing" << endl;
}

The compiler error

...is something like undefined structure 'Impl' (it's German: Undefinierte Struktur 'Impl'). When compiling the file check_shared.cpp the compiler stops in the file boost/checked_delete.hpp in the typedef of this function:

template<class T> inline void checked_delete(T * x)
{
    // intentionally complex - simplification causes regressions
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete x;
}

The output (expected)

This output I'm getting when using boost::share_ptr, showing that ctr and dtr are called as expected.

ctr Impl
ctr Containing
dtr Containing
dtr Impl
Wolf
  • 9,679
  • 7
  • 62
  • 108
  • 2
    "What am I doing wrong?" - not showing us the code or the error message (or describing the unexpected behaviour, or whatever problem you're having). – Mike Seymour May 19 '15 at 11:07
  • 2
    You're using a 13 year old compiler. Try with a modern compiler instead? :) – jalf May 19 '15 at 11:09
  • @jalf Absolutely. That's what I'm trying: getting rid of it, but the project still has to be maintained. – Wolf May 19 '15 at 11:23
  • The error message is still incomplete. The part of interest is the function that triggers the deletion of the scoped_ptr in the first place. – ComicSansMS May 19 '15 at 11:31
  • @ComicSansMS I tried to provide more details about the compiler error. – Wolf May 19 '15 at 11:44

2 Answers2

2

This is most definitely due to a compiler bug. Unfortunately it still exists in C++ Builder XE8. See this related problem here, and the answer from Andy Prowl: Is it valid for a C++ compiler to implicitly instantiate ALL member functions of a template class?

Problem reported to Embarcadero here: bcc32 compiler causes undefined behavior when using std::auto_ptr with PIMPL idiom because template instantiation rules do not follow C++ spec

If you can use one of the newer clang-based compilers from Embarcadero, you should be ok. My test case passes in the 64-bit clang compiler.

UPDATE: We've worked around the issue by writing our own smart pointer class that's similar to unique_ptr in that it contains a checked_delete function that is not implemented inline inside the class. Instead, you have to explicitly instantiate the checked_delete function inside ONE translation unit that also contains the private class implementation. A macro is used to help with that. That is:

template<class X> class unique_ptr_bcc32 {
    // Deletes the object using operator delete:
    private: static void checked_delete(X* p);
    // <snip>
}

Later, in a CPP file:

class PIMPL { /* snip implementation */ };

// You can simplify this by way of a macro:
template <> void unique_ptr_bcc32<PIMPL>::checked_delete(PIMPL* p) {
    typedef char type_must_be_complete[sizeof(PIMPL) ? 1 : -1];
    static_cast<void>(sizeof(type_must_be_complete));
    delete p;
}

This leaves no room for funny business by the compiler when it comes to template instantiation and deleting.

Community
  • 1
  • 1
James Johnston
  • 9,264
  • 9
  • 48
  • 76
1

This should work as long as the "implementation" class is complete at any point at which the smart pointer might be destroyed. This happens not just in the destructor, but in the constructors - these have to destroy the pointer member if they exit due to an exception.

So make sure that your constructors and destructor are defined in the source file, after the implementation class's definition.

(This is based on a guess you get a compilation error due to trying to destroy an incomplete type. If you get a different error, or unexpected runtime behaviour, or the change doesn't fix it, then please update the question to demonstrate the actual problem.)

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • I've added the code, would you please have a look at it again? Thanks. – Wolf May 19 '15 at 11:22
  • @Wolf: The code you've posted works for me (when I add the missing `#include "SmartPtrTest.h"` and `main` function). Either your ancient compiler is doing something weird (perhaps looking for the definition in the context of the template definition instead of the instantiation point), or the code you've posted isn't the code you're compiling. – Mike Seymour May 19 '15 at 11:31
  • Thanks for checking. So it seems to be a compiler issue, maybe it is due to some flaws in the implementation of the STL shipped with BCB6... – Wolf May 19 '15 at 11:53
  • @Wolf: BCB6 did not ship with Boost included. The code you showed does not use BCB's STL at all. – Remy Lebeau May 19 '15 at 19:35
  • @RemyLebeau I was not sure about this. I thought that boost itself uses some elements of the STL, I didn't analyse the scoped_ptr template yet. – Wolf May 20 '15 at 09:12