ฉันคิดว่ามันเป็นกลยุทธ์ที่ไม่ดีที่จะทำให้เป็นผลมาจาก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 อาจเพียงพอ