2

私は基本的に提案を実装しました。私の質問は、それが実行されたか、実行された場合、どこで実行されたかということです。および/または私がやっていることを行うためのより良い方法はありますか? この投稿の長さについて申し訳ありません。コードを提供する以外に、私のアプローチを説明するより良い方法を知りませんでした。

以前に pimpl という質問をしました: pimpl でポインターへのポインターを回避しますか?

interfaceここでもう一度その質問を説明するために、基本的に、インターフェースと実装があるとしましょうimpl。さらに、pimpl イディオムのように、impl.

これを c++0x で行う方法はunique_ptrinterfaceを指す で発言することimplです。のメソッドの実際の実装は にinterfaceは含まれていません。それらはのインターフェイスと実装の両方とともにmain.cpp、たとえば で個別にコンパイルされます。interface.cppimpl

このクラスは、ポインターが存在しないかのように設計されているため、ユーザーに対して効果的に透過的です。表記法ではなく、表記法を使用して.メソッドを呼び出します->。コピーする場合は、ディープ コピーの回路図を実装します。

しかし、実際にこのピンプルへの共有ポインターが必要な場合はどうなるか考えていました。を実行することもできますがshared_ptr<interface>、その場合は shared_ptr から unique_ptr を使用することになり、それは少しばかげていると思いました。insideshared_ptrの代わりに を使用することもできますが、それでも関数呼び出しの表記法を使用し、ポインターのようには見えないため、浅いコピーを行うとユーザーを驚かせる可能性があります。unique_ptrinterface.

インターフェイスと互換性のある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は実際には ashared_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(1 つのパラメータ を持ち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()、引数なしの create がコンパイルされ、それ以外の場合は呼び出されることはありません。implmain から呼び出したい他のコンストラクターがある場合は、ここでそれらを定義できます (例: my_unique_pimpl::create(int(0)))。

をもう一度見るとmain.cpp、 がどのようunique_pimplに作成されるかがわかります。しかし、他の結合方法を作成することもできます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

createfor実際には、二重のリダイレクトなしでshared_pimplreal を返すことに注意してください。shared_ptrの static_cast はget_impl()めちゃくちゃです。悲しいことに、継承ツリーを2段階上に移動してから実装に1段階下る以外に、より良い方法を知りませんでした。

たとえば、侵入ポインター用の他の「HANDLER」クラスを作成したり、すべてのヘッダーファイルを従来の方法で含める必要がある単純なスタック割り当て結合を作成したりすることを想像できます。そうすれば、ユーザーは pimpl に対応しているが pimpl を必要としないクラスを作成できます。

すべてのファイルを zip からダウンロードできますここ. それらは現在のディレクトリに抽出されます。gcc 4.4.5 と gcc 4.6.0 の両方で問題なく動作しました。

ですから、私が言ったように、どんな提案やコメントもいただければ幸いです。これが行われた場合 (おそらく私よりも優れています)、私に指示していただければ、それは素晴らしいことです。

4

1 に答える 1

3

それは、本当に、私にはひどく複雑に思えます...

あなた.が提案するセマンティクスでは、「インターフェース」を 2 回定義する必要があります。

  • あなたのために一度Proxy
  • 1回は基本クラス

これは DRY の直接の違反であり、利益はほとんどありません!

std::shared_ptr共有所有権の場合にのみ、使用するよりもクラスを使用することにあまり意味がありません。

私自身が pimpl 実装テンプレートを作成した理由は 1 つあります。これは、不完全な型の値セマンティクスを取得するために、 shared_ptr deleter実装 + ディープ コピー セマンティクスを適応させるためでした。

コードにヘルパーのレイヤーを追加すると、閲覧がより困難になります。

于 2011-04-14T16:50:30.820 に答える