これは、C ++の「pImpl」ベースのクラス階層に適したアプローチですか?


9

インターフェイスと実装を分離したいクラス階層があります。私の解決策は、インターフェイスのハンドルクラス階層と実装の非パブリッククラス階層の2つの階層を持つことです。基本ハンドルクラスには実装へのポインターがあり、派生ハンドルクラスは、派生型のポインターにキャストします(関数を参照getPimpl())。

これは、2つの派生クラスを持つ基本クラスの私のソリューションのスケッチです。より良い解決策はありますか?

ファイル「Base.h」:

#include <memory>

class Base {
protected:
    class Impl;
    std::shared_ptr<Impl> pImpl;
    Base(Impl* pImpl) : pImpl{pImpl} {};
    ...
};

class Derived_1 final : public Base {
protected:
    class Impl;
    inline Derived_1* getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
public:
    Derived_1(...);
    void func_1(...) const;
    ...
};

class Derived_2 final : public Base {
protected:
    class Impl;
    inline Derived_2* getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
public:
    Derived_2(...);
    void func_2(...) const;
    ...
};

ファイル「Base.cpp」:

class Base::Impl {
public:
    Impl(...) {...}
    ...
};

class Derived_1::Impl final : public Base::Impl {
public:
    Impl(...) : Base::Impl(...) {...}
    void func_1(...) {...}
    ...
};

class Derived_2::Impl final : public Base::Impl {
public:
    Impl(...) : Base::Impl(...) {...}
    void func_2(...) {...}
    ...
};

Derived_1::Derived_1(...) : Base(new Derived_1::Impl(...)) {...}
Derived_1::func_1(...) const { getPimpl()->func_1(...); }

Derived_2::Derived_2(...) : Base(new Derived_2::Impl(...)) {...}
Derived_2::func_2(...) const { getPimpl()->func_2(...); }

これらのクラスのうち、ライブラリ/コンポーネントの外部から見えるのはどれですか?の場合のみBase、通常の抽象基本クラス(「インターフェース」)とpimplのない具体的な実装で十分な場合があります。
D.ジュルカウ2017年

@ D.Jurcau基本クラスと派生クラスはすべて公開されます。明らかに、実装クラスはそうしません。
Steve Emmerson 2017年

なぜダウンキャスト?基本クラスはここでは奇妙な位置にあり、型安全性が改善され、コードが少ない共有ポインターに置き換えることができます。
バシレフ2017年

@Basilevsわかりません。パブリック基本クラスは、pimplイディオムを使用して実装を非表示にします。共有ポインターに置き換えると、ポインターをキャストしたり複製したりせずにクラス階層を維持できる方法がわかりません。コード例を提供できますか?
Steve Emmerson 2017年

ダウンキャストを複製する代わりに、ポインタを複製することを提案します。
バシレフ2017年

回答:


1

Derived_1::Implから派生させるのは悪い戦略だと思いますBase::Impl

Pimplイディオムを使用する主な目的は、クラスの実装の詳細を隠すことです。Derived_1::Implから派生させることでBase::Impl、あなたはその目的を打ち破りました。これで、の実装はにBase依存するだけでなくBase::Impl、の実装Derived_1もに依存しBase::Implます。

より良い解決策はありますか?

それは、どのトレードオフが許容できるかに依存します。

解決策1

作成しImplたクラスは、完全に独立しました。これは、Implクラスへのポインタが2つあることを意味BaseDerived_Nます。

class Base {

   protected:
      Base() : pImpl{new Impl()} {}

   private:
      // It's own Impl class and pointer.
      class Impl { };
      std::shared_ptr<Impl> pImpl;

};

class Derived_1 final : public Base {
   public:
      Derived_1() : Base(), pImpl{new Impl()} {}
      void func_1() const;
   private:
      // It's own Impl class and pointer.
      class Impl { };
      std::shared_ptr<Impl> pImpl;
};

解決策2

クラスをハンドルとしてのみ公開します。クラスの定義と実装をまったく公開しないでください。

公開ヘッダーファイル:

struct Handle {unsigned long id;};
struct Derived1_tag {};
struct Derived2_tag {};

Handle constructObject(Derived1_tag tag);
Handle constructObject(Derived2_tag tag);

void deleteObject(Handle h);

void fun(Handle h, Derived1_tag tag);
void bar(Handle h, Derived2_tag tag); 

ここに簡単な実装があります

#include <map>

class Base
{
   public:
      virtual ~Base() {}
};

class Derived1 : public Base
{
};

class Derived2 : public Base
{
};

namespace Base_Impl
{
   struct CompareHandle
   {
      bool operator()(Handle h1, Handle h2) const
      {
         return (h1.id < h2.id);
      }
   };

   using ObjectMap = std::map<Handle, Base*, CompareHandle>;

   ObjectMap& getObjectMap()
   {
      static ObjectMap theMap;
      return theMap;
   }

   unsigned long getNextID()
   {
      static unsigned id = 0;
      return ++id;
   }

   Handle getHandle(Base* obj)
   {
      auto id = getNextID();
      Handle h{id};
      getObjectMap()[h] = obj;
      return h;
   }

   Base* getObject(Handle h)
   {
      return getObjectMap()[h];
   }

   template <typename Der>
      Der* getObject(Handle h)
      {
         return dynamic_cast<Der*>(getObject(h));
      }
};

using namespace Base_Impl;

Handle constructObject(Derived1_tag tag)
{
   // Construct an object of type Derived1
   Derived1* obj = new Derived1;

   // Get a handle to the object and return it.
   return getHandle(obj);
}

Handle constructObject(Derived2_tag tag)
{
   // Construct an object of type Derived2
   Derived2* obj = new Derived2;

   // Get a handle to the object and return it.
   return getHandle(obj);
}

void deleteObject(Handle h)
{
   // Get a pointer to Base given the Handle.
   //
   Base* obj = getObject(h);

   // Remove it from the map.
   // Delete the object.
   if ( obj != nullptr )
   {
      getObjectMap().erase(h);
      delete obj;
   }
}

void fun(Handle h, Derived1_tag tag)
{
   // Get a pointer to Derived1 given the Handle.
   Derived1* obj = getObject<Derived1>(h);
   if ( obj == nullptr )
   {
      // Problem.
      // Decide how to deal with it.

      return;
   }

   // Use obj
}

void bar(Handle h, Derived2_tag tag)
{
   Derived2* obj = getObject<Derived2>(h);
   if ( obj == nullptr )
   {
      // Problem.
      // Decide how to deal with it.

      return;
   }

   // Use obj
}

長所と短所

最初のアプローチでDerivedは、スタック内にクラスを構築できます。2番目のアプローチでは、それはオプションではありません。

最初のアプローチでDerivedは、スタック内のを構築および破棄するための2つの動的割り当てと割り当て解除のコストが発生します。Derivedヒープからオブジェクトを構築および破棄する場合は、割り当てと割り当て解除のコストがさらに1つ発生します。2番目の方法では、オブジェクトごとに1つの動的割り当てと1つの割り当て解除のコストしかかかりません。

最初のアプローチでは、virtualメンバー関数is を使用できるようになりますBase。2番目のアプローチでは、それはオプションではありません。

私のおすすめ

私は最初のソリューションを使用するので、クラスの階層とvirtualメンバー関数をBase少し高価ですが使用できます。


0

ここでわかる唯一の改善点は、具象クラスに実装フィールドを定義させることです。抽象基本クラスで必要な場合は、具象クラスに実装しやすい抽象プロパティを定義できます。

Base.h

class Base {
protected:
    class Impl;
    virtual std::shared_ptr<Impl> getImpl() =0;
    ...
};

class Derived_1 final : public Base {
protected:
    class Impl1;
    std::shared_ptr<Impl1> pImpl
    virtual std::shared_ptr<Base::Impl> getImpl();
public:
    Derived_1(...);
    void func_1(...) const;
    ...
};

Base.cpp

class Base::Impl {
public:
    Impl(...) {...}
    ...
};

class Derived_1::Impl1 final : public Base::Impl {
public:
    Impl(...) : Base::Impl(...) {...}
    void func_1(...) {...}
    ...
};

std::shared_ptr<Base::Impl> Derived_1::getImpl() { return pPimpl; }
Derived_1::Derived_1(...) : pPimpl(std::make_shared<Impl1>(...)) {...}
void Derived_1::func_1(...) const { pPimpl->func_1(...); }

これは私にとってより安全なようです。大きな木がある場合virtual std::shared_ptr<Impl1> getImpl1() =0は、木の真ん中で紹介することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.