Saya pikir ini adalah strategi yang buruk untuk Derived_1::Impl
diambil Base::Impl
.
Tujuan utama menggunakan idiom Pimpl adalah untuk menyembunyikan detail implementasi kelas. Dengan membiarkan Derived_1::Impl
berasal Base::Impl
, Anda telah mengalahkan tujuan itu. Sekarang, implementasi tidak hanya Base
bergantung pada Base::Impl
, implementasi Derived_1
juga tergantung pada Base::Impl
.
Apakah ada solusi yang lebih baik?
Itu tergantung pada trade-off apa yang dapat Anda terima.
Solusi 1
Jadikan Impl
kelas benar-benar independen. Ini akan menyiratkan bahwa akan ada dua petunjuk ke Impl
kelas - satu masuk Base
dan 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 Derived
kelas 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 Derived
di stack. Jika Anda membangun dan merusak Derived
objek 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 virtual
fungsi anggota ini Base
. Dengan pendekatan kedua, itu bukan pilihan.
Saran saya
Saya akan pergi dengan solusi pertama sehingga saya dapat menggunakan hirarki kelas dan virtual
fungsi anggota Base
meskipun itu sedikit lebih mahal.
Base
, kelas dasar abstrak normal ("antarmuka") dan implementasi konkret tanpa jerawat mungkin cukup.