インターフェイスを実装から分離したいクラス階層があります。私の解決策は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(...); }
Derived_1::Impl
をBase::Impl
から派生させるのは悪い戦略だと思います。
Pimplイディオムを使用する主な目的は、クラスの実装の詳細を隠すことです。 Derived_1::Impl
をBase::Impl
から派生させることにより、その目的を打ち破っています。現在、Base
の実装はBase::Impl
に依存しているだけでなく、Derived_1
の実装もBase::Impl
に依存しています。
より良い解決策はありますか?
それは、どのトレードオフが許容できるかに依存します。
ソリューション1
Impl
クラスを完全に独立させます。これは、Impl
クラスへのポインタが2つあることを意味します。1つはBase
にあり、もう1つはDerived_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
メンバー関数がBase
を使用できるようになります。 2番目のアプローチでは、それはオプションではありません。
私の提案
最初のソリューションを使用するので、少し高価ですが、virtual
でクラス階層とBase
メンバー関数を使用できます。
ここでわかる唯一の改善点は、具象クラスに実装フィールドを定義させることです。抽象基本クラスで必要な場合は、具象クラスに実装しやすい抽象プロパティを定義できます。
_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;
...
};
_
_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
を導入することもできます。