2

我基本上已经实施了一个提案,我的问题是,它是否已经完成,如果是,在哪里?和/或有更好的方法来做我正在做的事情吗?很抱歉这篇文章的长度,除了提供代码之外,我不知道有更好的方法来解释我的方法。

我之前问过pimpl 的问题:避免使用 pimpl 指向指针?

在这里再次解释这个问题,基本上,假设我们有一个 interfaceinterface和一个 implementation impl。此外,像 pimpl 习惯用法一样,我们希望能够单独编译impl.

现在在 c++0x 中执行此操作的一种方法是unique_ptrinterfacewhich 指向impl. 方法的实际实现interface不包含在其中,它们与接口和实现一起main.cpp单独编译。interface.cppimpl

我们设计这个类,就好像指针不存在一样,因此它对用户有效透明。我们使用.符号来调用方法,而不是->符号,如果我们要复制,我们实现深拷贝原理图。

但后来我在想,如果我真的想要一个指向这个 pimpl 的共享指针怎么办。我可以这样做shared_ptr<interface>,但后来我有一个 shared_ptr 到一个 unique_ptr,我认为这有点傻。我可以使用shared_ptr而不是unique_ptrinside interface,但它仍然会使用.符号调用函数,并且看起来不像指针,所以当它浅拷贝时可能会让用户感到惊讶。

我开始认为最好有一些通用模板类来连接接口和任何兼容的and对的X相应实现,处理大量 pimpl 样板文件。YXY

所以下面是我试图做到的方式。

首先,我将从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_ptrpimpl 实现。x2实际上是 a shared_ptr,没有 a 引起的双指针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 参数创建,否则永远不会被调用。如果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

请注意,createforshared_pimpl实际上返回一个 real shared_ptr,没有双重重定向。中的 static_castget_impl()是一团糟,遗憾的是我不知道更好的方法,除了在继承树上走两步,然后再往下走两步到实现。

例如,我可以想象为侵入式指针制作其他“HANDLER”类,甚至是一个简单的堆栈分配连接,它需要以传统方式包含所有头文件。这样,用户可以编写 pimpl 就绪但不需要 pimpl 的类。

您可以从此处的 zip 文件中下载所有文件。他们将解压到当前目录。您需要使用具有一些 c++0x 功能的东西进行编译,gcc 4.4.5 和 gcc 4.6.0 对我来说都很好。

所以就像我说的,任何建议/意见都将不胜感激,如果这已经完成(可能比我做得更好),如果你能指导我,那就太好了。

4

1 回答 1

3

它,真的,对我来说似乎非常复杂......

您提出的.语义需要定义“接口”两次:

  • 一次为你Proxy
  • 一次用于基类

这是对 DRY 的直接违反,收益如此之少!

std::shared_ptr仅在共享所有权的情况下,我认为使用您的类而不是使用类没有多大意义。

我自己写了一个pimpl实现模板是有原因的,这是为了适配shared_ptr deleter实现+深拷贝语义,从而得到不完整类型的值语义。

在代码中添加帮助层最终会使其更难浏览。

于 2011-04-14T16:50:30.820 回答