Salah satu masalah pimpl adalah penalti kinerja menggunakannya (alokasi memori tambahan, anggota data yang tidak bersebelahan, tipuan tambahan, dll.). Saya ingin mengusulkan variasi pada idiom jerawat yang akan menghindari hukuman kinerja ini dengan mengorbankan tidak mendapatkan semua manfaat jerawat. Idenya adalah untuk meninggalkan semua anggota data pribadi di kelas itu sendiri dan hanya memindahkan metode pribadi ke kelas pimpl. Manfaat dibandingkan dengan jerawat dasar adalah bahwa memori tetap berdekatan (tidak ada tipuan tambahan). Manfaat dibandingkan dengan tidak menggunakan jerawat sama sekali adalah:
- Itu menyembunyikan fungsi pribadi.
- Anda dapat menyusunnya sehingga semua fungsi ini akan memiliki tautan internal dan memungkinkan kompiler untuk lebih mengoptimalkannya secara agresif.
Jadi ide saya adalah membuat mucikari mewarisi dari kelas itu sendiri (kedengarannya agak gila saya tahu, tapi tahan dengan saya). Akan terlihat seperti ini:
Dalam file Ah:
class A
{
A();
void DoSomething();
protected: //All private stuff have to be protected now
int mData1;
int mData2;
//Not even a mention of a PImpl in the header file :)
};
Dalam file A.cpp:
#define PCALL (static_cast<PImpl*>(this))
namespace //anonymous - guarantees internal linkage
{
struct PImpl : public A
{
static_assert(sizeof(PImpl) == sizeof(A),
"Adding data members to PImpl - not allowed!");
void DoSomething1();
void DoSomething2();
//No data members, just functions!
};
void PImpl::DoSomething1()
{
mData1 = bar(mData2); //No Problem: PImpl sees A's members as it's own
DoSomething2();
}
void PImpl::DoSomething2()
{
mData2 = baz();
}
}
A::A(){}
void A::DoSomething()
{
mData2 = foo();
PCALL->DoSomething1(); //No additional indirection, everything can be completely inlined
}
Sejauh yang saya lihat sama sekali tidak ada hukuman kinerja dalam menggunakan ini vs no pimpl dan beberapa kemungkinan peningkatan kinerja dan antarmuka file header bersih. Salah satu kelemahan ini memiliki vs standar jerawat adalah bahwa Anda tidak dapat menyembunyikan anggota data sehingga perubahan pada anggota data tersebut masih akan memicu kompilasi ulang semua yang tergantung pada file header. Tetapi menurut saya, itu bisa mendapatkan manfaat itu atau manfaat kinerja karena memiliki anggota yang berdekatan dalam memori (atau melakukan peretasan ini)- "Mengapa Percobaan # 3 Disesalkan"). Peringatan lain adalah bahwa jika A adalah kelas templated maka sintaksanya akan mengganggu (Anda tahu, Anda tidak dapat menggunakan mData1 secara langsung, Anda perlu melakukan ini-> mData1, dan Anda harus mulai menggunakan nama ketik dan mungkin kata kunci templat untuk jenis dependen dan tipe templated, dll.). Namun peringatan lain adalah bahwa Anda tidak dapat lagi menggunakan pribadi di kelas asli, hanya anggota yang dilindungi, sehingga Anda tidak dapat membatasi akses dari kelas warisan apa pun, tidak hanya jerawat. Saya mencoba tetapi tidak bisa menyelesaikan masalah ini. Sebagai contoh, saya mencoba membuat pimpl sebagai kelas templat teman dengan harapan membuat deklarasi teman cukup luas untuk memungkinkan saya mendefinisikan kelas pimpl yang sebenarnya di namespace anonim, tetapi itu tidak berhasil. Jika ada yang tahu bagaimana menjaga agar data anggota tetap pribadi dan masih memungkinkan kelas pimpl bawaan yang didefinisikan dalam ruang nama anonim untuk mengaksesnya, saya benar-benar ingin melihatnya! Itu akan menghilangkan reservasi utama saya dari menggunakan ini.
Saya merasa, bahwa peringatan ini dapat diterima untuk manfaat dari apa yang saya usulkan.
Saya mencoba mencari online untuk beberapa referensi untuk idiom "fungsi-hanya jerawat" ini tetapi tidak dapat menemukan apa pun. Saya sangat tertarik dengan apa yang orang pikirkan tentang ini. Apakah ada masalah lain dengan ini atau alasan saya tidak boleh menggunakan ini?
MEMPERBARUI:
Saya telah menemukan proposal ini yang sedikit banyak berusaha mencapai apa yang sebenarnya saya lakukan, tetapi melakukannya dengan mengubah standar. Saya sepenuhnya setuju dengan proposal itu dan berharap itu akan menjadi standar (saya tidak tahu apa-apa tentang proses itu jadi saya tidak tahu bagaimana kemungkinan itu terjadi). Saya lebih suka melakukan ini melalui mekanisme bahasa bawaan. Proposal juga menjelaskan manfaat dari apa yang saya coba capai jauh lebih baik daripada saya. Itu juga tidak memiliki masalah melanggar enkapsulasi seperti saran saya (pribadi -> dilindungi). Tetap saja, sampai proposal itu membuatnya menjadi standar (jika itu pernah terjadi), saya pikir saran saya memungkinkan untuk mendapatkan manfaat itu, dengan peringatan yang saya cantumkan.
UPDATE2:
Salah satu jawaban menyebutkan KPP sebagai alternatif yang mungkin untuk mendapatkan beberapa manfaat (optimisasi lebih agresif saya kira). Saya tidak benar-benar yakin apa yang terjadi di berbagai optimisasi kompiler melewati tapi saya punya sedikit pengalaman dengan kode yang dihasilkan (saya menggunakan gcc). Cukup dengan meletakkan metode pribadi di kelas asli akan memaksa mereka untuk memiliki hubungan eksternal.
Saya mungkin salah di sini, tetapi cara saya menafsirkannya adalah bahwa pengoptimal waktu kompilasi tidak dapat menghilangkan fungsi bahkan jika semua instance panggilannya benar-benar diuraikan di dalam TU itu. Untuk beberapa alasan bahkan KPP menolak untuk menyingkirkan definisi fungsi bahkan jika tampaknya semua contoh panggilan dalam seluruh biner yang terhubung semuanya digarisbawahi. Saya menemukan beberapa referensi yang menyatakan bahwa itu karena linker tidak tahu apakah Anda masih akan memanggil fungsi menggunakan pointer fungsi (meskipun saya tidak mengerti mengapa linker tidak dapat mengetahui bahwa alamat metode itu tidak pernah diambil ).
Ini tidak terjadi jika Anda menggunakan saran saya dan menempatkan metode pribadi di jerawat di dalam namespace anonim. Jika itu diuraikan, fungsi TIDAK akan muncul di (dengan -O3, yang mencakup -finline-fungsi) file objek.
Cara saya memahaminya, pengoptimal, ketika memutuskan apakah akan fungsi inline atau tidak, memperhitungkan dampaknya pada ukuran kode. Jadi, dengan menggunakan saran saya, saya membuatnya sedikit "lebih murah" bagi pengoptimal untuk menyesuaikan metode pribadi tersebut.
PCALL
perilaku tidak terdefinisi. Anda tidak dapat melemparkan sebuahA
kePImpl
dan menggunakannya kecuali objek yang mendasarinya sebenarnya bertipePImpl
. Namun, kecuali saya salah, pengguna hanya akan membuat objek bertipeA
.