Pimpl с умными указателями в классе с конструктором шаблона: странная проблема неполного типа - PullRequest
5 голосов
/ 07 февраля 2011

При использовании интеллектуальных указателей с идиомой pImpl, как в

struct Foo
{
private:
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl;
};

, очевидная проблема заключается в том, что Foo::Impl является неполным в точке, где генерируется деструктор Foo.

Компиляторы обычно выдают там предупреждение, и boost::checked_delete, который используется внутренне интеллектуальными указателями Boost, статически утверждает, что класс Foo::Impl завершен, и выдает ошибку, если это не так.

Чтобы скомпилировать приведенный выше пример, необходимо написать

struct Foo
{
    ~Foo();

private:
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl;
};

и реализовать пустой Foo::~Foo в файле реализации, где Foo::Impl завершено.Это преимущество умных указателей над голыми указателями, потому что мы не можем не реализовать деструктор.

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

// File Bar.h
#ifndef BAR_H
#define BAR_H 1

#include <vector>
#include <boost/scoped_ptr.hpp>

struct Bar
{
    template <typename I>
    Bar(I begin, I end);

    ~Bar();

private:
    struct Impl;
    boost::scoped_ptr<Impl> pImpl;

    void buildImpl(std::vector<double>&);
};


template <typename I>
Bar::Bar(I begin, I end)
{
    std::vector<double> tmp(begin, end);
    this->buildImpl(tmp);
}

#endif // BAR_H

// File Bar.cpp
#include "Bar.h"

struct Bar::Impl
{
    std::vector<double> v;
};

void Bar::buildImpl(std::vector<double>& v)
{
    pImpl.reset(new Impl);
    pImpl->v.swap(v);
}

Bar::~Bar() {}

// File Foo.h
#ifndef FOO_H
#define FOO_H 1

#include <boost/scoped_ptr.hpp>


struct Foo
{
    Foo();
    ~Foo();

private:
    struct Impl;
    boost::scoped_ptr<Impl> pImpl;
};

#endif // FOO_H

// File Foo.cpp
#include "Foo.h"

struct Foo::Impl
{};


Foo::Foo() : pImpl(new Impl)
{}


Foo::~Foo() {}


// File Main.cpp
#include "Foo.h"
#include "Bar.h"

int main()
{
    std::vector<double> v(42);
    Foo f;
    Bar b(v.begin(), v.end());
}

При компиляции этого примера с Visual Studio 2005 SP1, Я получаю ошибку с Bar, но не с Foo:

1>Compiling...
1>main.cpp
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2027: use of undefined type 'Bar::Impl'
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl'
1>        c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(80) : see reference to function template instantiation 'void boost::checked_delete<T>(T *)' being compiled
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>        c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(76) : while compiling class template member function 'boost::scoped_ptr<T>::~scoped_ptr(void)'
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(16) : see reference to class template instantiation 'boost::scoped_ptr<T>' being compiled
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2118: negative subscript
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(34) : warning C4150: deletion of pointer to incomplete type 'Bar::Impl'; no destructor called
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl'

Я попробую это с недавним GCC, как только я вернусь домой.

Я непонять, что происходит: в точке, где определен деструктор (т. е. в Bar.cpp), доступно определение Bar::Impl, поэтому проблем быть не должно.Почему это работает с Foo, а не с Bar?

Что мне здесь не хватает?

Ответы [ 2 ]

5 голосов
/ 07 февраля 2011

Это деструктор boost::shared_ptr<>, который требует завершения объекта при использовании средства удаления boost::checked_deleter<>.Поскольку вы помещаете конструктор диапазона Bar::Bar(I begin, I end) в заголовочный файл, компилятор должен сгенерировать код, который уничтожает уже созданные элементы, если ваш конструктор создает исключение, следовательно, он пытается создать экземпляр boost::scoped_ptr<T>::~scoped_ptr(void) при создании экземпляра этого шаблона.Менее полезно использовать умные указатели с pimpl.Поскольку обычно вам все равно нужно предоставить деструктор, вы можете также поместить delete pimpl в этот деструктор и покончить с этим.

1 голос
/ 11 июня 2012

Из Boost Boost документация :

Обратите внимание, что scoped_ptr требует, чтобы T был полным типом во время уничтожения, а shared_ptr - нет.

Переключитесь на shared_ptr, и все должно быть в порядке - не нужно иметь деструктор (пустой или нет). Если вы хотите сделать класс не копируемым, чтобы он имел семантику, которую вы получили бы от scoped_ptr, наследуйте (приватно) от boost :: noncopyable.

...