PImpl deyimi pratikte gerçekten kullanılıyor mu?


165

Herb Sutter'in "Olağanüstü C ++" kitabını okuyorum ve bu kitapta pImpl deyimini öğrendim. Temel olarak, fikir, a'nın privatenesneleri için bir yapı oluşturmak classve bunları derleme süresini azaltmak için dinamik olarak ayırmaktır (ve ayrıca özel uygulamaları daha iyi gizlemek).

Örneğin:

class X
{
private:
  C c;
  D d;  
} ;

şu şekilde değiştirilebilir:

class X
{
private:
  struct XImpl;
  XImpl* pImpl;       
};

ve CPP'de tanım:

struct X::XImpl
{
  C c;
  D d;
};

Bu oldukça ilginç görünüyor, ancak daha önce hiç çalıştığım şirketlerde ne de kaynak kodunu gördüğüm açık kaynaklı projelerde bu tür bir yaklaşım görmedim. Bu tekniğin pratikte gerçekten kullanıldığını merak ediyorum.

Her yerde mi yoksa dikkatle mi kullanmalıyım? Ve bu tekniğin gömülü sistemlerde kullanılması önerilir (performansın çok önemli olduğu yerlerde)?


Bu aslında X'in (soyut) bir arayüz ve Ximpl'in uygulama olduğuna karar vermekle aynı mıdır? struct XImpl : public X. Bu bana daha doğal geliyor. Kaçırdığım başka bir sorun var mı?
Aaron McDaid

@AaronMcDaid: Bu benzerdir, ancak (a) üye işlevlerinin sanal olması gerekmemesi ve (b) örneklemek için bir fabrikaya veya uygulama sınıfının tanımına ihtiyacınız olmaması gibi avantajlara sahiptir.
Mike Seymour

2
@AaronMcDaid Pimpl deyimi sanal işlev çağrılarını önler. Aynı zamanda biraz daha C ++ - ish (bazı C ++ - ish anlayışı için); fabrika işlevlerinden ziyade kurucuları çağırırsınız. Her ikisini de kullandım, mevcut kod tabanında ne olduğuna bağlı olarak - pimpl deyimi (başlangıçta Cheshire kedi deyimi olarak adlandırıldı ve Herb'in en az 5 yıl önce onun tanımını önceden yazıyor) daha uzun bir geçmişe sahip ve daha fazla görünüyor C ++ 'da yaygın olarak kullanılır, ancak aksi takdirde her ikisi de çalışır.
James Kanze

30
C ++ 'da pimpl const unique_ptr<XImpl>yerine uygulanmalıdır XImpl*.
Neil G

1
"ne çalıştığım şirketlerde ne de açık kaynaklı projelerde daha önce bu tür bir yaklaşım görmemiştim." Qt neredeyse hiç kullanmıyor.
ManuelSchneid3r

Yanıtlar:


132

Bu tekniğin pratikte gerçekten kullanıldığını merak ediyorum. Her yerde mi yoksa dikkatle mi kullanmalıyım?

Tabii ki kullanılır. Projemde neredeyse her sınıfta kullanıyorum.


PIMPL deyimini kullanma nedenleri:

İkili uyumluluk

Bir kitaplık geliştirirken XImpl, istemcinizle ikili uyumluluğu bozmadan alanlar ekleyebilir / değiştirebilirsiniz (çökmeler anlamına gelir!). XSınıfa yeni alanlar eklediğinizde sınıfın ikili düzeni değişmediğinden Ximpl, küçük sürüm güncellemelerinde kitaplığa yeni işlevler eklemek güvenlidir.

Tabii ki, ikili uyumluluğu bozmadan X/ XImplbozmadan yeni genel / özel sanal olmayan yöntemler de ekleyebilirsiniz , ancak bu standart başlık / uygulama tekniğiyle aynıdır.

Veri gizleme

Bir kitaplık, özellikle de tescilli bir kitaplık geliştiriyorsanız, kitaplığınızın genel arabirimini uygulamak için başka hangi kitaplıkların / uygulama tekniklerinin kullanıldığını açıklamak istenmeyebilir. Fikri Mülkiyet sorunları nedeniyle veya kullanıcıların uygulama hakkında tehlikeli varsayımlar almaya cazip gelebileceğine ya da sadece korkunç döküm hileleri kullanarak kapsüllemeyi kırabileceğine inandığınız için. PIMPL bunu çözer / azaltır.

Derleme zamanı

Sınıfa Xalanlar ve / veya yöntemler eklediğinizde / kaldırdığınızda XImpl(standart teknikte özel alanların / yöntemlerin eklenmesiyle eşleşir ) yalnızca kaynak (uygulama) dosyasının yeniden oluşturulması gerektiğinden derleme süresi azalır . Uygulamada, bu yaygın bir işlemdir.

Standart üstbilgi / uygulama tekniğiyle (PIMPL olmadan), yeni bir alan eklediğinizde , ayırmanın boyutunu ayarlaması gerektiğinden, Xayırdığı her istemcinin X(yığın veya yığın üzerinde) yeniden derlenmesi gerekir. Eh, hiç X tahsis etmez her müşteri de yeniden derlenmesi gerekir, ancak (istemci tarafında çıkan kod aynı olacaktır) sadece havai var.

Dahası, standart üstbilgi / uygulama ayrımı XClient1.cpp, özel bir yöntem X::foo()eklendiğinde Xve X.hdeğiştirildiğinde bile yeniden derlenmelidir , ancak XClient1.cppkapsülleme nedenleriyle bu yöntemi çağıramazsınız! Yukarıdaki gibi, saf yük ve gerçek hayattaki C ++ yapı sistemlerinin nasıl çalıştığı ile ilgilidir.

Tabii ki, sadece yöntemlerin uygulamasını değiştirdiğinizde yeniden derlemeye gerek yoktur (çünkü başlığa dokunmazsınız), ancak bu standart başlık / uygulama tekniğiyle aynıdır.


Bu tekniğin gömülü sistemlerde kullanılması önerilir mi (performansın çok önemli olduğu yerlerde)?

Bu, hedefinizin ne kadar güçlü olduğuna bağlıdır. Ancak bu sorunun tek cevabı: ne kazandığınızı ve kaybettiğinizi ölçün ve değerlendirin. Ayrıca, müşterileriniz tarafından gömülü sistemlerde kullanılması amaçlanan bir kütüphane yayınlamıyorsanız, yalnızca derleme süresi avantajının geçerli olduğunu göz önünde bulundurun!


16
+1 çünkü çalıştığım şirkette ve aynı nedenlerle yaygın olarak kullanılıyor.
Benoit

9
ayrıca, ikili uyumluluk
Ambroz Bizjak

9
Qt kütüphanesinde bu yöntem akıllı işaretçi durumlarında da kullanılır. Böylece QString, içeriğini kendi içinde değişmez bir sınıf olarak tutar. Ortak sınıf "kopyalandığında" özel sınıfın tamamı yerine özel üyenin işaretçisi kopyalanır. Bu özel sınıflar daha sonra akıllı işaretçiler kullanır, bu nedenle tam sınıf kopyalama yerine işaretçi kopyalama nedeniyle büyük ölçüde geliştirilmiş performansın yanı sıra temel olarak sınıfların çoğuyla çöp toplama elde edersiniz
Timothy Baldridge

8
Daha da fazlası, pimpl deyimiyle Qt, tek bir ana sürümde (çoğu durumda) hem ileri hem de geri ikili uyumluluğu koruyabilir. IMO, bunu kullanmak için açık ara en önemli nedendir.
whitequark

1
Aynı API'yı koruyabileceğiniz için platforma özgü kodların uygulanması için de kullanışlıdır.
doc

49

Görünüşe göre, birçok kütüphane, en azından bazı sürümler için API'larında sabit kalmak için bunu kullanıyor.

Ancak her şeye gelince, hiçbir yerde dikkatli bir şekilde hiçbir şey kullanmamalısınız. Kullanmadan önce daima düşünün. Size hangi avantajları sağladığını ve ödediğiniz fiyata değip değmeyeceğini değerlendirin.

Size sağlayabileceği avantajlar :

  • paylaşılan kütüphanelerin ikili uyumluluğunun korunmasına yardımcı olur
  • belirli dahili ayrıntıları gizleme
  • azalan yeniden derleme döngüleri

Bunlar sizin için gerçek avantajlar olabilir veya olmayabilir. Benim gibi, birkaç dakikalık derleme zamanı umurumda değil. Son kullanıcılar genellikle her zaman bir kez ve en başından derledikleri için de yapmazlar.

Olası dezavantajlar (ayrıca, uygulamaya ve sizin için gerçek dezavantaj olup olmadıklarına bağlı olarak):

  • Saf varyanttan daha fazla ayırma nedeniyle bellek kullanımında artış
  • daha fazla bakım çabası (en azından yönlendirme işlevlerini yazmanız gerekir)
  • performans kaybı (derleyici, sınıfınızın saf bir uygulamasında olduğu gibi satır içi öğeleri sıralayamayabilir)

Bu yüzden her şeye dikkatlice bir değer verin ve kendiniz için değerlendirin. Benim için, neredeyse her zaman pimpl deyimini kullanmanın çabaya değmeyeceği ortaya çıkıyor. Şahsen kullandığım tek bir durum var (veya en azından benzer bir şey):

Linux statçağrı için benim C ++ sarıcı . Burada, C başlığındaki yapı #defines, ayarlananlara bağlı olarak farklı olabilir . Ve benim sarıcı başlığında beri sadece, hepsi kontrol edemez #include <sys/stat.h>benim de .cxxdosyaya ve bu sorunlardan kaçınmak.


2
Arayüz kod sistemini bağımsız hale getirmek için neredeyse her zaman sistem arayüzleri için kullanılmalıdır. Benim File(bilgilerin çoğunu açığa sınıfı statUnix altında dönecekti) örneğin, Windows ve Unix hem altında aynı arabirimi kullanır.
James Kanze

5
@JamesKanze: Orada bile şahsen bir an oturdum #ifdefve sargıyı olabildiğince inceltmek için birkaç s'ye sahip olmanın yeterli olup olmadığını düşünürdüm . Ancak herkesin farklı hedefleri var, önemli olan şey, bir şeyi körü körüne takip etmek yerine düşünmek için zaman ayırmaktır.
PlazmaHH

31

Mallar hakkında tüm diğerleriyle aynı fikirde olun, ancak bir kanıt sunmama izin verin: şablonlarla iyi çalışmaz .

Bunun nedeni, şablon örneklemenin, örneklemenin gerçekleştiği yerdeki tam bildirimi gerektirmesidir. (CPP dosyalarında tanımlanmış şablon yöntemlerini görmemenin ana nedeni budur)

Yine de templetized alt sınıflarına başvurabilirsiniz, ancak hepsini dahil etmeniz gerektiğinden, derleme üzerindeki "uygulama ayırma" nın her avantajı (her yere platoform spesifik kodları dahil etmekten kaçınmak, derlemeyi kısaltmak) kaybolur.

Klasik OOP (kalıtım tabanlı) için iyi bir paradigmadır, ancak genel programlama (uzmanlık tabanlı) için değil.


4
Daha kesin olmalısınız: PIMPL sınıflarını şablon türü bağımsız değişkenleri olarak kullanırken kesinlikle sorun yoktur . Yalnızca uygulama sınıfının kendisinin dış sınıfın şablon bağımsız değişkenlerinde parametreleştirilmesi gerekiyorsa, hala özel bir sınıf olsa bile artık arabirim başlığından gizlenemez. Şablon argümanını silebiliyorsanız, kesinlikle "uygun" PIMPL yapabilirsiniz. Tür silme ile PIMPL'i şablon olmayan bir temel sınıfta da yapabilir ve bundan sonra şablon sınıfının türetilmesini sağlayabilirsiniz.
Monica

22

Diğer insanlar zaten teknik yukarı / aşağı yönlü sağlamışlardır, ancak aşağıdakilerin kayda değer olduğunu düşünüyorum:

Her şeyden önce dogmatik olmayın. Durumunuz için pImpl çalışıyorsa, bunu kullanın - çünkü " gerçekten uygulamayı gizlediğinden daha iyi OO " vb. Kullanmayın.

kapsülleme kod içindir, insanlar için değil ( kaynak )

Sadece nerede ve neden açık kaynak kodlu bir yazılım örneği vermek gerekirse: OpenThreads, OpenSceneGraph tarafından kullanılan iş parçacığı kütüphanesi . Ana fikir, <Thread.h>platforma özgü tüm kodları başlıktan (örn. ) Kaldırmaktır , çünkü dahili durum değişkenleri (örn. İplik tutamaçları) platformdan platforma farklılık gösterir. Bu şekilde, diğer platformların kendine özgü özellikleri hakkında bilgi sahibi olmadan kütüphanenize karşı kod derleyebilirsiniz, çünkü her şey gizlidir.


12

Ben esas olarak PIMPL diğer modüller tarafından bir API olarak kullanılmak üzere maruz sınıflar için düşünün. Bunun birçok faydası vardır, çünkü PIMPL uygulamasında yapılan değişikliklerin yeniden derlenmesi projenin geri kalanını etkilemez. Ayrıca, API sınıfları için ikili uyumluluğu desteklerler (bir modül uygulamasındaki değişiklikler bu modüllerin istemcilerini etkilemez, yeni uygulamanın aynı ikili arabirime (PIMPL tarafından maruz bırakılan arabirime) sahip olması nedeniyle yeniden derlenmeleri gerekmez.

Her sınıf için PIMPL kullanmaya gelince, tüm bu faydaların bir bedeli vardır çünkü uygulama yöntemlerine erişmek için fazladan bir dolaylama gerekir.


"Uygulama yöntemlerine erişmek için fazladan bir dolaylama düzeyi gereklidir." Bu?
xaxxon

@xaxxon evet, öyle. yöntemler düşük seviyeli ise pimpl daha yavaştır. örneğin, sıkı bir döngü içinde yaşayan şeyler için asla kullanmayın.
Erik Aronesty

@xaxxon Genel olarak fazladan bir seviyenin gerekli olduğunu söyleyebilirim. Eğer satır içi çizgi hayır. Ancak satır içi farklı bir dll derlenmiş kodunda bir seçenek olmaz.
Ghita

5

Bence bu ayırma için en temel araçlardan biri.

Gömülü projede (SetTopBox) pimpl (ve Exceptional C ++ 'dan diğer birçok deyim) kullanıyordum.

Projemizdeki bu idoim'in özel amacı XImpl sınıfının kullandığı tipleri gizlemekti. Özellikle, farklı başlıkların içeri çekileceği farklı donanım uygulamalarının ayrıntılarını gizlemek için kullandık. Bir platform için farklı ve diğeri için farklı XImpl sınıf uygulamalarımız vardı. X sınıfının düzeni, platfromdan bağımsız olarak aynı kaldı.


4

Eskiden bu tekniği çok kullanıyordum ama kendimi ondan uzaklaşırken buldum.

Tabii ki uygulama detaylarını sınıfınızın kullanıcılarından uzak tutmak iyi bir fikirdir. Ancak bunu, sınıf kullanıcılarından soyut bir arabirim kullanmaya ve uygulama ayrıntılarının somut sınıf olmasını sağlayarak da yapabilirsiniz.

PImpl'nin avantajları:

  1. Bu arayüzün sadece bir uygulaması olduğu varsayılarak, soyut sınıf / somut uygulama kullanılmaması daha açıktır

  2. Birkaç sınıf aynı "impl" e erişecek şekilde bir sınıflar paketiniz (modül) varsa, ancak modül kullanıcıları yalnızca "açık" sınıfları kullanır.

  3. Kötü bir şey olduğu varsayılırsa v-table yok.

PImpl'de bulduğum dezavantajlar (soyut arayüzün daha iyi çalıştığı yerlerde)

  1. Yalnızca bir "üretim" uygulamanız olsa da, soyut bir arabirim kullanarak birim sınamasında çalışan bir "sahte" uygulama oluşturabilirsiniz.

  2. (En büyük sorun). Unique_ptr ve hareketli günlerden önce, pImpl'in nasıl saklanacağı konusunda kısıtlı seçenekler vardı. Ham bir işaretçi ve sınıfınızın kopyalanamaz olmasıyla ilgili sorunlarınız vardı. Eski bir auto_ptr, ileri bildirilen sınıfla çalışmaz (zaten tüm derleyicilerde değil). Böylece, insanlar sınıfınızı kopyalanabilir hale getirmek için güzel olan shared_ptr kullanmaya başladılar, ancak elbette her iki kopya da beklemeyeceğiniz aynı paylaşımlı paylaştı. Bu yüzden çözüm genellikle iç için ham işaretçiyi kullanmak ve sınıfı kopyalanamaz hale getirmek ve bunun yerine bir paylaşılan_ptr döndürmekti. Yani iki yeni çağrı. (Aslında eski shared_ptr verilen 3 size ikinci bir tane verdi).

  3. Constness bir üye imlecine yayılmadığından teknik olarak gerçekten doğru değil.

Genel olarak bu nedenle yıllar içinde pImpl'den ve bunun yerine soyut arayüz kullanımına (ve örnekleri oluşturmak için fabrika yöntemlerine) geçtim.


3

Diğerlerinin de belirttiği gibi, Pimpl deyimi, maalesef performans kaybı (ek işaretçi dolaylaması) ve ek bellek ihtiyacı (üye işaretçisinin kendisi) ile tam bilgi gizleme ve derleme bağımsızlığına ulaşmaya izin verir. Ek maliyet, gömülü yazılım geliştirmede, özellikle de belleğin mümkün olduğunca tasarruf edilmesi gereken senaryolarda kritik olabilir. C ++ soyut sınıflarını arabirimler olarak kullanmak, aynı maliyetle aynı faydalara yol açacaktır. Bu, C benzeri arayüzlere (parametre olarak opak bir işaretçi ile küresel yöntemler) tekrar etmeden, ek kaynak dezavantajları olmadan gerçek bilgi gizleme ve derleme bağımsızlığına sahip olmanın mümkün olmadığı büyük bir C ++ eksikliğini gösterir: kullanıcıları tarafından dahil edilmesi gereken bir sınıfın beyanı,


3

İşte bu deyimin çok yardımcı olduğu gerçek bir senaryo. Kısa süre önce DirectX 11'in yanı sıra mevcut DirectX 9 desteğimi bir oyun motorunda desteklemeye karar verdim. Motor zaten birçok DX özelliğini kapladı, bu nedenle DX arabirimlerinin hiçbiri doğrudan kullanılmadı; sadece başlıklarda özel üye olarak tanımlandılar. Motor, DLL'leri uzantılar olarak kullanır, klavye, fare, oyun çubuğu ve komut dosyası desteği ekler; Bu DLL'lerin çoğu doğrudan DX kullanmadıysa da, sadece DX maruz bırakan başlıkları çektiği için DX için bilgi ve bağlantıya ihtiyaç duyuyorlardı. DX 11 eklenirken, bu karmaşıklık önemli ölçüde artmaktaydı, ancak gereksiz yere. DX üyelerini sadece kaynakta tanımlanan bir Pimpl'e taşımak bu dayatmayı ortadan kaldırdı. Kütüphane bağımlılıklarındaki bu azalmanın yanı sıra,


2

Uygulamada birçok projede kullanılır. Yararlılığı büyük ölçüde projenin türüne bağlıdır. Bu kullanarak daha belirgin projelerden biri olan Qt temel fikir kullanıcıdan gizlemek uygulanması veya platformspecific kodu (Qt kullanarak diğer geliştiriciler) etmektir.

Bu asil bir fikir ama bunun gerçek bir dezavantajı var: hata ayıklama Özel uygulamalarda gizli kod üstün kalitede olduğu sürece, bu iyi, ama orada hatalar varsa, kullanıcı / geliştiricinin bir sorunu var, çünkü uygulamaların kaynak kodu olsa bile, gizli bir uygulamaya sadece aptalca bir işaretçi.

Yani neredeyse tüm tasarım kararlarında olduğu gibi artıları ve eksileri var.


9
aptal ama yazıyor ... neden hata ayıklayıcıdaki kodu takip edemiyorsun?
amca

2
Genel olarak, Qt kodunda hata ayıklamak için Qt'yi kendiniz oluşturmanız gerekir. Bunu yaptıktan sonra, PIMPL yöntemlerine adım atmada ve PIMPL verilerinin içeriğini denetlemede sorun yoktur.
Monica

0

Görebildiğim bir fayda, programcının belirli işlemleri oldukça hızlı bir şekilde gerçekleştirmesine izin vermesidir:

X( X && move_semantics_are_cool ) : pImpl(NULL) {
    this->swap(move_semantics_are_cool);
}
X& swap( X& rhs ) {
    std::swap( pImpl, rhs.pImpl );
    return *this;
}
X& operator=( X && move_semantics_are_cool ) {
    return this->swap(move_semantics_are_cool);
}
X& operator=( const X& rhs ) {
    X temporary_copy(rhs);
    return this->swap(temporary_copy);
}

PS: Umarım hareket semantiği yanlış anlamıyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.