Saya pikir ini adalah strategi yang buruk untuk Derived_1::Impldiambil Base::Impl.
Tujuan utama menggunakan idiom Pimpl adalah untuk menyembunyikan detail implementasi kelas. Dengan membiarkan Derived_1::Implberasal Base::Impl, Anda telah mengalahkan tujuan itu. Sekarang, implementasi tidak hanya Basebergantung pada Base::Impl, implementasi Derived_1juga tergantung pada Base::Impl.
Apakah ada solusi yang lebih baik?
Itu tergantung pada trade-off apa yang dapat Anda terima.
Solusi 1
Jadikan Implkelas benar-benar independen. Ini akan menyiratkan bahwa akan ada dua petunjuk ke Implkelas - satu masuk Basedan satu lagi masuk 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;
};
Solusi 2
Ekspos kelas hanya sebagai pegangan. Jangan memaparkan definisi dan implementasi kelas sama sekali.
File header publik:
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);
Inilah implementasi cepat
#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
}
Pro dan kontra
Dengan pendekatan pertama, Anda bisa membuat Derivedkelas di tumpukan. Dengan pendekatan kedua, itu bukan pilihan.
Dengan pendekatan pertama, Anda harus mengeluarkan biaya dua alokasi dinamis dan deallokasi untuk membangun dan merusak a Deriveddi stack. Jika Anda membangun dan merusak Derivedobjek dari heap Anda, dikenakan biaya satu alokasi lagi dan deallokasi. Dengan pendekatan kedua, Anda hanya dikenakan biaya satu alokasi dinamis dan satu alokasi untuk setiap objek.
Dengan pendekatan pertama, Anda mendapatkan kemampuan untuk menggunakan virtualfungsi anggota ini Base. Dengan pendekatan kedua, itu bukan pilihan.
Saran saya
Saya akan pergi dengan solusi pertama sehingga saya dapat menggunakan hirarki kelas dan virtualfungsi anggota Basemeskipun itu sedikit lebih mahal.
Base, kelas dasar abstrak normal ("antarmuka") dan implementasi konkret tanpa jerawat mungkin cukup.