Почему boost :: scoped_ptr предотвращает использование PIMPL для BCB6?

Я пытаюсь использовать boost::scoped_ptr с моим классом реализации, который виден только в файле cpp содержащего класса. Содержащий класс имеет явно определенный деструктор (не встроенный), но мой компилятор (Borland C ++ 5.6.4) не может скомпилироваться.

Если я использую boost::shared_ptr вместо этого тот же пример компилируется и запускается, как и ожидалось.

Что я делаю неправильно?


Редактировать: извините за то, что вы забыли показать исходный код, ошибку компилятора и (ожидаемый) вывод здесь:

Исходный код

файл check_shared.cpp:

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

файл 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;
};

файл 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;
}

Ошибка компилятора

…это что-то вроде undefined structure 'Impl' (это немецкий: Undefinierte Struktur ‘Impl’). При компиляции файла check_shared.cpp компилятор останавливается в файле boost/checked_delete.hpp в typedef этой функции:

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;
}

Выход (ожидаемый)

Этот вывод я получаю при использовании boost::share_ptr, показывая, что ctr и dtr вызываются, как и ожидалось.

ctr Impl
ctr Containing
dtr Containing
dtr Impl

1

Решение

Это должно работать до тех пор, пока класс «реализации» завершен в любой точке, в которой интеллектуальный указатель может быть уничтожен. Это происходит не только в деструкторе, но и в конструкторах — они должны уничтожить член-указатель, если они выходят из-за исключения.

Поэтому убедитесь, что ваши конструкторы и деструктор определены в исходном файле после определения класса реализации.

(Это основано на предположении, что вы получаете ошибку компиляции из-за попытки уничтожить неполный тип. Если вы получаете другую ошибку, или неожиданное поведение во время выполнения, или изменение не исправляет это, пожалуйста, обновите вопрос, чтобы продемонстрировать актуальная проблема.)

1

Другие решения

Это определенно связано с ошибкой компилятора. К сожалению, он все еще существует в C ++ Builder XE8. Смотрите эту связанную проблему здесь, и ответ от Энди Prowl: Допустимо ли для компилятора C ++ неявно создавать экземпляры ВСЕХ функций-членов шаблонного класса?

О проблеме сообщили в Embarcadero здесь: Компилятор bcc32 вызывает неопределенное поведение при использовании std :: auto_ptr с идиомой PIMPL, поскольку правила создания шаблонов не соответствуют спецификации C ++

Если вы можете использовать один из более новых компиляторов на основе clang от Embarcadero, у вас все будет в порядке. Мой тестовый пример проходит в 64-битном компиляторе clang.

ОБНОВИТЬ: Мы решили эту проблему, написав собственный класс интеллектуальных указателей, который похож на unique_ptr в том, что он содержит checked_delete функция, которая не реализована внутри класса. Вместо этого вы должны явно создать экземпляр checked_delete функция внутри ОДНОЙ единицы перевода, которая также содержит реализацию частного класса. Макрос используется, чтобы помочь с этим. То есть:

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

Позже, в файле CPP:

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;
}

Это не оставляет места для забавного бизнеса со стороны компилятора, когда дело доходит до создания и удаления шаблона.

1