PImpl (Pointer to Implementation) tasarım deseni, aynı zamanda Opak İşaretçi olarak bilinir, C++'da sınıfın arayüzü ile uygulanmasını ayırmak için bir araç olarak ortaya çıkmıştır. Bu, ikili arayüz (ABI) uyumluluğunu sağlamak, derleme süresini hızlandırmak ve sınıfın kullanıcılarından uygulama ayrıntılarını gizlemek için özellikle önemlidir.
Meselenin tarihi.
Birçok C++ projesinde, bu sınıfların kamu arayüzünü değiştirmeden, müşteri uygulamalarını yeniden derlemeden, sınıf uygulamalarını değiştirmek gerekir. Sorun, başlık dosyalarındaki herhangi bir değişikliğin, bağımlı modüllerin yeniden derlenmesini gerektirmesidir; bu, büyük kod tabanlarında son derece maliyetli olabilir. PImpl, yeniden derlemeyi en aza indirmeye yardımcı olur ve daha iyi kapsülleme sağlar.
Sorun.
Başlık dosyasındaki özel üye tanımlama, derleme sırasında bu üyelerin tümünün bilinmesini gerektirir. Onları genişletirken veya değiştirirken, bu başlığı dahil eden tüm dosyaların yeniden derlenmesi gerekecektir. Ayrıca, bu, müşterinin uygulama/ yapı ayrıntılarını açığa çıkarır, bu da güvenlik ve mimari bütünlük üzerinde olumsuz bir etki yaratabilir.
Çözüm.
PImpl, bir.forward olarak deklare edilen uygulama yapısına (Impl struct/class) işaretçi kullanarak uygulama ayrıntılarını gizler; bu yapı cpp dosyasında tanımlanır. Bu, uygulamayı arayüzü etkilemeden değiştirmeye olanak tanır.
Kod örneği:
// Widget.h class Widget { public: Widget(); ~Widget(); void doSomething(); private: struct Impl; Impl* pimpl; // opak işaretçi }; // Widget.cpp #include "Widget.h" struct Widget::Impl { int secret; }; Widget::Widget() : pimpl(new Impl{42}) {} // gizlilik içeride Widget::~Widget() { delete pimpl; } void Widget::doSomething() { pimpl->secret += 1; }
Anahtar özellikler:
PImpl'de ham işaretçi yerine std::unique_ptr kullanmak mümkün mü?
Evet, modern ve güvenli bir yöntem olarak std::unique_ptr (veya sahipliği paylaşmak gerekiyorsa std::shared_ptr) kullanmak en iyisidir. Bu, hafızayı düzgün bir şekilde yönetmeyi sağlar ve ham işaretçi için açıkça bir yıkıcı/kopyalama operatörü yazmayı gerektirmez:
private: std::unique_ptr<Impl> pimpl;
PImpl'li bir sınıfı kopyalanamaz, ama taşınabilir yapmak mümkün mü?
Evet, taşıma yapıcı/operatörü sağlarsanız, ancak kopyalamayı silerseniz mümkündür. Örneğin:
Widget(Widget&&) noexcept = default; Widget& operator=(Widget&&) noexcept = default; Widget(const Widget&) = delete; Widget& operator=(const Widget&) = delete;
PImpl kullanımıyla performansta ek bir yük ortaya çıkıyor mu?
Evet, işaretçinin çözülmesi ve ek dinamik bellek tahsisi (heap allocation) nedeniyle. Kritik performansa sahip yapılar için bu önemli bir dezavantaj olabilir.
Büyük bir firma, tüm sınıflar için PImpl uyguladı, basit veri yapıları da dahil, bu da sürekli işaretçilerin çözülmesi nedeniyle basit işlemlerin önemli ölçüde yavaşlamasına neden oldu.
Artılar:
Eksikler:
Uzun ömürlü bir kullanıcı arayüzü kütüphanesi projesinde, PImpl yalnızca sıkça değişen iç yapıya sahip karmaşık widget'lar için uygulandı ve üçüncü taraf müşteriler için kararlı bir ABI korundu.
Artılar:
Eksikler: