나는 그것을 Derived_1::Impl
이끌어 내는 것이 좋지 않은 전략이라고 생각합니다 Base::Impl
.
Pimpl 관용구의 주요 목적은 클래스의 구현 세부 사항을 숨기는 것입니다. Derived_1::Impl
에서 파생 되도록함으로써 Base::Impl
해당 목표를 달성했습니다. 이제의 구현은에 Base
의존 할뿐만 아니라 Base::Impl
의 구현 Derived_1
도에 의존합니다 Base::Impl
.
더 나은 해결책이 있습니까?
이는 귀하에게 어떤 트레이드 오프가 허용되는지에 달려 있습니다.
해결책 1
Impl
수업을 완전히 독립적으로 만듭니다 . 이것은 Impl
클래스에 대한 두 개의 포인터가 있음을 의미합니다 .Base
와 다른 하나에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
스택에 클래스를 . 두 번째 방법으로는 옵션이 아닙니다.
첫 번째 접근 방식에서는 두 가지 동적 할당 및 할당 취소 비용이 발생합니다. Derived
스택 . Derived
힙에서 오브젝트 를 구성하고 파괴하는 경우 하나 이상의 할당 및 할당 취소 비용이 발생합니다. 두 번째 방법에서는 모든 객체에 대해 하나의 동적 할당과 하나의 할당 해제 비용 만 발생합니다.
첫 번째 방법으로 virtual
멤버 함수 를 사용할 수 있습니다 Base
. 두 번째 방법으로는 옵션이 아닙니다.
나의 제안
조금 더 비싸더라도 클래스 계층 구조 및 virtual
멤버 함수를 사용할 수 있도록 첫 번째 솔루션을 사용합니다 Base
.
Base
, 일반적인 추상 기본 클래스 ( "인터페이스")와 pimpl이없는 구체적인 구현으로 충분할 수 있습니다.