Tôi nghĩ rằng đó là một chiến lược kém để làm cho Derived_1::Impl
xuất phát từBase::Impl
.
Mục đích chính của việc sử dụng thành ngữ Pimpl là để ẩn các chi tiết triển khai của một lớp. Bằng cách cho Derived_1::Impl
xuất phát từ Base::Impl
, bạn đã đánh bại mục đích đó. Bây giờ, không chỉ thực hiện Base
phụ thuộc vào Base::Impl
, việc thực hiện Derived_1
cũng phụ thuộc vào Base::Impl
.
Có một giải pháp tốt hơn?
Điều đó phụ thuộc vào những gì đánh đổi được chấp nhận với bạn.
Giải pháp 1
Làm cho Impl
các lớp hoàn toàn độc lập. Điều này sẽ ngụ ý rằng sẽ có hai con trỏ tới Impl
các lớp - một trong Base
và một khác trong 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;
};
Giải pháp 2
Chỉ hiển thị các lớp như tay cầm. Đừng để lộ các định nghĩa và triển khai lớp.
Tệp tiêu đề công khai:
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);
Đây là triển khai nhanh
#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
}
Ưu và nhược điểm
Với cách tiếp cận đầu tiên, bạn có thể xây dựng Derived
các lớp trong ngăn xếp. Với cách tiếp cận thứ hai, đó không phải là một lựa chọn.
Với cách tiếp cận đầu tiên, bạn phải chịu chi phí cho hai phân bổ động và phân bổ động để xây dựng và phá hủy một Derived
trong ngăn xếp. Nếu bạn xây dựng và phá hủy một Derived
đối tượng từ heap bạn, sẽ phải chịu chi phí cho một lần phân bổ và phân bổ thêm. Với cách tiếp cận thứ hai, bạn chỉ phải chịu chi phí cho một phân bổ động và một lần phân bổ cho mọi đối tượng.
Với cách tiếp cận đầu tiên, bạn có khả năng sử dụng virtual
chức năng thành viên Base
. Với cách tiếp cận thứ hai, đó không phải là một lựa chọn.
Đề xuất của tôi
Tôi sẽ đi với giải pháp đầu tiên để tôi có thể sử dụng hệ thống phân cấp lớp và các virtual
hàm thành viên Base
mặc dù nó đắt hơn một chút.
Base
, một lớp cơ sở trừu tượng bình thường ("giao diện") và các triển khai cụ thể mà không có pimpl có thể là đủ.