Запрашиваются комментарии / предложения по структуре Pimpl - PullRequest
2 голосов
/ 14 апреля 2011

Я в основном реализовал предложение, мой вопрос, было ли оно сделано, и если да, то где?И / или есть ли лучший способ сделать то, что я делаю?Извините за длину этого поста, я не знал лучшего способа объяснить мой подход, кроме предоставления кода.

Ранее я задавал вопрос pimpl: как избежать указателя на указатель с помощью pimpl?

Чтобы пояснить этот вопрос здесь, в основном, скажем, у нас есть интерфейс interface и реализация impl.Кроме того, как и идиома pimpl, мы хотим иметь возможность отдельно компилировать impl.

. Теперь способ сделать это в c ++ 0x состоит в том, чтобы сказать unique_ptr в interfaceкоторый указывает на impl.Фактическая реализация методов interface не включена в main.cpp, они скомпилированы отдельно, скажем, interface.cpp, вместе с интерфейсом и реализацией impl.

Мы проектируем этот класс какесли указатель отсутствует, он фактически прозрачен для пользователя.Мы используем нотацию . для вызова методов, а не нотацию ->, и, если мы хотим копировать, мы реализуем схемы глубокого копирования.

Но потом я подумал, что, если я действительно хотел бы иметь общий указательк этому прыщу.Я мог бы просто сделать shared_ptr<interface>, но тогда у меня был бы shared_ptr для unique_ptr, и я подумал, что это немного глупо.Я мог бы использовать shared_ptr вместо unique_ptr внутри interface, но тогда он по-прежнему использовал бы функции вызова нотации . и на самом деле не был бы похож на указатель, поэтому может удивить пользователей, когда он копирует мелко.

Я начал думать, что было бы неплохо иметь какой-то общий шаблонный класс, который соединял бы интерфейс X и соответствующую реализацию Y для любой совместимой пары X и Y, обрабатывающей многошаблонные прыщи.

Итак, вот как я пытался это сделать.

Сначала я начну с main.cpp:

#include "interface.hpp"
#include "unique_pimpl.hpp"
#include "shared_pimpl.hpp"

int main()
{
  auto x1 = unique_pimpl<interface, impl>::create();
  x1.f();

  auto x2(x1);
  x2 = x1;

  auto x3(std::move(x1)); 
  x3 = std::move(x1);

  auto y1 = shared_pimpl<interface, impl>::create();
  y1->f();

  auto y2(y1);
  y2 = y1;

  auto y3(std::move(y1));
  y3 = std::move(y1);
}

По сути, x1 - это стандартная реализация unique_ptr pimpl.x2 на самом деле shared_ptr, без двойного указателя, вызванного unique_ptr.Многие из заданий и конструкторов были только для тестирования.

Сейчас interface.hpp:

#ifndef INTERFACE_HPP
#define INTERFACE_HPP

#include "interface_macros.hpp"

class impl;

INTERFACE_START(interface);

  void f();

INTERFACE_END;

#endif

interface_macros.hpp:

#ifndef INTERFACE_MACROS_HPP
#define INTERFACE_MACROS_HPP

#include <utility>

#define INTERFACE_START(class_name) \
template <class HANDLER> \
class class_name : public HANDLER \
{ \
public: \
  class_name(HANDLER&& h = HANDLER()) : HANDLER(std::move(h)) {} \
  class_name(class_name<HANDLER>&& x) : HANDLER(std::move(x)) {} \
  class_name(const class_name<HANDLER>& x) : HANDLER(x) {}

#define INTERFACE_END }

#endif

interface_macros.hpp просто содержитнекоторый шаблонный код, который требуется для структуры, которую я разработал.Интерфейс принимает HANDLER в качестве аргумента шаблона и делает его базовым классом. Эти конструкторы просто гарантируют, что вещи перенаправляются в базовый HANDLER, где происходит действие.Конечно, само по себе interface не будет иметь никаких членов и конструкторов, просто некоторые общедоступные функции-члены.

Теперь interface.cpp - наш другой файл.На самом деле он содержит реализацию interface, и, несмотря на его название, также интерфейс и реализацию impl.Я пока не буду перечислять файл полностью, но первое, что он включает в себя: interface_impl.hpp (извините за запутанное название).

Вот interface_impl.hpp:

#ifndef INTERFACE_IMPL_HPP
#define INTERFACE_IMPL_HPP

#include "interface.hpp"
#include "impl.hpp"

template <class HANDLER>
void interface<HANDLER>::f() { this->get_impl().f(); }

#endif

Обратите внимание на вызов метода get_impl().Это будет предоставлено HANDLER позже.

impl.hpp содержит как интерфейс, так и реализацию impl.Я мог бы отделить их, но не видел необходимости.Вот impl.hpp:

#ifndef IMPL_HPP
#define IMPL_HPP

#include "interface.hpp"
#include <iostream>

class impl
{
public:
  void f()  { std::cout << "Hello World" << std::endl; };
};

#endif

Теперь давайте взглянем на unique_pimpl.hpp.Помните, что это было включено в main.cpp, поэтому наша основная программа имеет определение этого.

unique_pimpl.hpp:

#ifndef UNIQUE_PIMPL_HPP
#define UNIQUE_PIMPL_HPP

#include <memory>

template
<
  template<class> class INTERFACE,
  class IMPL
>
class unique_pimpl
{
public:
  typedef IMPL impl_type;
  typedef unique_pimpl<INTERFACE, IMPL> this_type;
  typedef INTERFACE<this_type> super_type;

  template <class ...ARGS>
  static super_type create(ARGS&& ...args);
protected:
  unique_pimpl(const this_type&);
  unique_pimpl(this_type&& x);
  this_type& operator=(const this_type&);
  this_type& operator=(this_type&& p);
  ~unique_pimpl();

  unique_pimpl(impl_type* p);
  impl_type& get_impl();
  const impl_type& get_impl() const;
private:
  std::unique_ptr<impl_type> p_;
};

#endif

Здесь мы передадим класс шаблона INTERFACE (которыйимеет один параметр, HANDLER, который мы будем заполнять здесь unique_pimpl), и класс IMPL (который в нашем случае impl).В этом классе находится unique_ptr.

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

Давайте посмотрим на unique_pimpl_impl.hpp:

#ifndef UNIQUE_PIMPL_IMPL_HPP
#define UNIQUE_PIMPL_IMPL_HPP

#include "unique_pimpl.hpp"

#define DEFINE_UNIQUE_PIMPL(interface, impl, type) \
template class unique_pimpl<interface, impl>; \
typedef unique_pimpl<interface, impl> type; \
template class interface< type >;

template < template<class> class INTERFACE, class IMPL> template <class ...ARGS>
typename unique_pimpl<INTERFACE, IMPL>::super_type 
unique_pimpl<INTERFACE, IMPL>::create(ARGS&&... args) 
  { return unique_pimpl<INTERFACE, IMPL>::super_type(new IMPL(std::forward<ARGS>(args)...)); }

template < template<class> class INTERFACE, class IMPL>
typename unique_pimpl<INTERFACE, IMPL>::impl_type& 
unique_pimpl<INTERFACE, IMPL>::get_impl() 
  { return *p_; }

template < template<class> class INTERFACE, class IMPL>
const typename unique_pimpl<INTERFACE, IMPL>::impl_type& 
unique_pimpl<INTERFACE, IMPL>::get_impl() const 
  { return *p_; }

template < template<class> class INTERFACE, class IMPL>
unique_pimpl<INTERFACE, IMPL>::unique_pimpl(typename unique_pimpl<INTERFACE, IMPL>::impl_type* p) 
  : p_(p) {}

template < template<class> class INTERFACE, class IMPL>
unique_pimpl<INTERFACE, IMPL>::~unique_pimpl() {}

template < template<class> class INTERFACE, class IMPL>
unique_pimpl<INTERFACE, IMPL>::unique_pimpl(unique_pimpl<INTERFACE, IMPL>&& x) : 
  p_(std::move(x.p_)) {}

template < template<class> class INTERFACE, class IMPL>
unique_pimpl<INTERFACE, IMPL>::unique_pimpl(const unique_pimpl<INTERFACE, IMPL>& x) : 
  p_(new IMPL(*(x.p_))) {}

template < template<class> class INTERFACE, class IMPL>
unique_pimpl<INTERFACE, IMPL>& unique_pimpl<INTERFACE, IMPL>::operator=(unique_pimpl<INTERFACE, IMPL>&& x) 
  { if (this != &x) { (*this).p_ = std::move(x.p_); } return *this; }

template < template<class> class INTERFACE, class IMPL>
unique_pimpl<INTERFACE, IMPL>& unique_pimpl<INTERFACE, IMPL>::operator=(const unique_pimpl<INTERFACE, IMPL>& x) 
  { if (this != &x) { this->p_ = std::unique_ptr<IMPL>(new IMPL(*(x.p_))); } return *this; }

#endif

Теперь многое из вышеперечисленного - просто код котельной плиты, и он делает то, что вы ожидаете.create(...) просто перенаправляет в конструктор impl, который в противном случае не был бы виден пользователю.Также есть макроопределение DEFINE_UNIQUE_PIMPL, которое мы можем использовать позже для создания экземпляров соответствующих шаблонов.

Теперь мы можем вернуться к interface.cpp:

#include "interface_impl.hpp"
#include "unique_pimpl_impl.hpp"
#include "shared_pimpl_impl.hpp"

// This instantates required functions

DEFINE_UNIQUE_PIMPL(interface, impl, my_unique_pimpl)

namespace
{
  void instantate_my_unique_pimpl_create_functions()
  {
    my_unique_pimpl::create();
  }
}

DEFINE_SHARED_PIMPL(interface, impl, my_shared_pimpl)

namespace
{
  void instantate_my_shared_pimpl_create_functions()
  {
    my_shared_pimpl::create();
  }
}

Это гарантирует, что всесоответствующие шаблоны скомпилированы instantate_my_unique_pimpl_create_functions() гарантирует, что мы скомпилируем 0-аргумент create, и в противном случае он никогда не будет вызываться.Если бы у impl были другие конструкторы, которые мы хотели бы вызвать из main, мы могли бы определить их здесь (например, my_unique_pimpl::create(int(0))).

Оглядываясь назад на main.cpp, вы теперь можете увидеть, как unique_pimpl s можетбыть создан.Но мы можем создать другие методы соединения, и вот shared_pimpl:

shared_pimpl.hpp:

#ifndef SHARED_PIMPL_HPP
#define SHARED_PIMPL_HPP

#include <memory>

template <template<class> class INTERFACE, class IMPL>
class shared_impl_handler;

template < template<class> class INTERFACE, class IMPL>
class shared_pimpl_get_impl
{
public:
  IMPL& get_impl();
  const IMPL& get_impl() const;
};

template
<
  template<class> class INTERFACE,
  class IMPL
>
class shared_pimpl
{
public:
  typedef INTERFACE< shared_pimpl_get_impl<INTERFACE, IMPL> > interface_type;
  typedef shared_impl_handler<INTERFACE, IMPL> impl_type;
  typedef std::shared_ptr<interface_type> return_type;

  template <class ...ARGS>
  static return_type create(ARGS&& ...args);
};

#endif

shared_pimpl_impl.hpp:

#ifndef SHARED_PIMPL_IMPL_HPP
#define SHARED_PIMPL_IMPL_HPP

#include "shared_pimpl.hpp"

#define DEFINE_SHARED_PIMPL(interface, impl, type) \
template class shared_pimpl<interface, impl>; \
typedef shared_pimpl<interface, impl> type; \
template class interface< shared_pimpl_get_impl<interface, impl> >;

template <template<class> class INTERFACE, class IMPL>
class shared_impl_handler : public INTERFACE< shared_pimpl_get_impl<INTERFACE, IMPL> >, public IMPL 
{
  public:
    template <class ...ARGS>
    shared_impl_handler(ARGS&&... args) : INTERFACE< shared_pimpl_get_impl<INTERFACE, IMPL> >(), IMPL(std::forward<ARGS>(args)...) {}
};

template < template<class> class INTERFACE, class IMPL> template <class ...ARGS>
typename shared_pimpl<INTERFACE, IMPL>::return_type shared_pimpl<INTERFACE, IMPL>::create(ARGS&&... args) 
  { return shared_pimpl<INTERFACE, IMPL>::return_type(new shared_pimpl<INTERFACE, IMPL>::impl_type(std::forward<ARGS>(args)...)); }

template < template<class> class INTERFACE, class IMPL>
IMPL& shared_pimpl_get_impl<INTERFACE, IMPL>::get_impl() 
  { return static_cast<IMPL&>(static_cast<shared_impl_handler<INTERFACE, IMPL>& >(static_cast<INTERFACE< shared_pimpl_get_impl<INTERFACE, IMPL> >&>(*this))); }

template < template<class> class INTERFACE, class IMPL>
const IMPL& shared_pimpl_get_impl<INTERFACE, IMPL>::get_impl() const 
  { return static_cast<const IMPL&>(static_cast<const shared_impl_handler<INTERFACE, IMPL>& >(static_cast<const INTERFACE<shared_pimpl_get_impl<INTERFACE, IMPL> >&>(*this))); }

#endif

Обратите внимание, что create для shared_pimpl фактически возвращает реальное shared_ptr без двойного перенаправления.Статическая трансляция в get_impl() - беспорядок, к сожалению, я не знал лучшего способа сделать это, кроме как пройти два шага вверх по дереву наследования и затем один шаг до реализации.

Я могу представить себе, как сделать другоеНапример, классы «HANDLER» для навязчивых указателей и даже простое объединенное выделение стека, которое требует, чтобы все заголовочные файлы были включены традиционным способом.Таким образом, пользователи могут писать классы, которые готовы для pimpl, но не требуют pimpl.

Вы можете скачать все файлы с zip здесь .Они будут извлечены в текущий каталог.Вам нужно будет скомпилировать что-нибудь с некоторыми функциями c ++ 0x, и gcc 4.4.5, и gcc 4.6.0 отлично сработали для меня.

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

1 Ответ

3 голосов
/ 14 апреля 2011

Это, на самом деле, кажется мне ужасно сложным ...

Семантика ., которую вы предлагаете, требует определения «интерфейса» дважды:

  • Один раз для вашего Proxy
  • Один раз для базового класса

Это прямое нарушение СУХОГО, так мало выгоды!

Я не вижу особого смысла в использовании вашегокласс с использованием, просто std::shared_ptr в случае совместного владения.

Есть одна причина, по которой я сам написал шаблон реализации pimpl, и это было для адаптации реализации shared_ptr deleter + глубокая семантика копирования, чтобы получить семантику значений для неполных типов.

Добавление слоев помощников в коде в конечном итоге усложняет просмотр.

...