Saya memiliki hierarki kelas yang ingin saya pisahkan antarmuka dari implementasinya. Solusi saya adalah memiliki dua hierarki: hierarki kelas pegangan untuk antarmuka dan hierarki kelas non-publik untuk implementasi. Kelas pegangan dasar memiliki pointer-to-implementasi yang kelas pegangan turunan dilemparkan ke pointer dari tipe turunan (lihat fungsi getPimpl()
).
Inilah sketsa solusi saya untuk kelas dasar dengan dua kelas turunan. Apakah ada solusi yang lebih baik?
File "Base.h":
#include <memory>
class Base {
protected:
class Impl;
std::shared_ptr<Impl> pImpl;
Base(Impl* pImpl) : pImpl{pImpl} {};
...
};
class Derived_1 final : public Base {
protected:
class Impl;
inline Derived_1* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_1(...);
void func_1(...) const;
...
};
class Derived_2 final : public Base {
protected:
class Impl;
inline Derived_2* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_2(...);
void func_2(...) const;
...
};
File "Base.cpp":
class Base::Impl {
public:
Impl(...) {...}
...
};
class Derived_1::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_1(...) {...}
...
};
class Derived_2::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_2(...) {...}
...
};
Derived_1::Derived_1(...) : Base(new Derived_1::Impl(...)) {...}
Derived_1::func_1(...) const { getPimpl()->func_1(...); }
Derived_2::Derived_2(...) : Base(new Derived_2::Impl(...)) {...}
Derived_2::func_2(...) const { getPimpl()->func_2(...); }
Base
, kelas dasar abstrak normal ("antarmuka") dan implementasi konkret tanpa jerawat mungkin cukup.Jawaban:
Saya pikir ini adalah strategi yang buruk untuk
Derived_1::Impl
diambilBase::Impl
.Tujuan utama menggunakan idiom Pimpl adalah untuk menyembunyikan detail implementasi kelas. Dengan membiarkan
Derived_1::Impl
berasalBase::Impl
, Anda telah mengalahkan tujuan itu. Sekarang, implementasi tidak hanyaBase
bergantung padaBase::Impl
, implementasiDerived_1
juga tergantung padaBase::Impl
.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 keImpl
kelas - satu masukBase
dan satu lagi masukDerived_N
.Solusi 2
Ekspos kelas hanya sebagai pegangan. Jangan memaparkan definisi dan implementasi kelas sama sekali.
File header publik:
Inilah implementasi cepat
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 merusakDerived
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 iniBase
. Dengan pendekatan kedua, itu bukan pilihan.Saran saya
Saya akan pergi dengan solusi pertama sehingga saya dapat menggunakan hirarki kelas dan
virtual
fungsi anggotaBase
meskipun itu sedikit lebih mahal.sumber
Satu-satunya perbaikan yang dapat saya lihat di sini adalah membiarkan kelas konkret menentukan bidang implementasi. Jika kelas dasar abstrak membutuhkannya, mereka dapat mendefinisikan properti abstrak yang mudah diterapkan di kelas konkret:
Base.h
Base.cpp
Ini tampaknya lebih aman bagi saya. Jika Anda memiliki pohon besar, Anda juga bisa memperkenalkannya
virtual std::shared_ptr<Impl1> getImpl1() =0
di tengah-tengah pohon.sumber