2008-09-13 18 views
104

संदर्भ सामग्री:"पीआईएमपीएल" मुहावरे का उपयोग क्यों किया जाना चाहिए?

PIMPL Idiom (कार्यान्वयन के लिए सूचक) कार्यान्वयन छुपा है, जिसमें एक सार्वजनिक वर्ग एक संरचना या वर्ग कि पुस्तकालय सार्वजनिक वर्ग का हिस्सा है बाहर नहीं देखा जा सकता लपेटता के लिए एक तकनीक है।

यह पुस्तकालय के उपयोगकर्ता से आंतरिक कार्यान्वयन विवरण और डेटा छुपाता है।

इस मुहावरे को लागू करते समय आप सार्वजनिक तरीकों को पिंपल वर्ग पर क्यों रखेंगे, न कि सार्वजनिक वर्ग के तरीके से सार्वजनिक वर्ग विधि कार्यान्वयन पुस्तकालय में संकलित किया जाएगा और उपयोगकर्ता के पास केवल हेडर फ़ाइल है?

उदाहरण के लिए, यह कोड Purr() इम्प्लोर क्लास पर कार्यान्वयन करता है और इसे भी लपेटता है।

पुरानी कक्षा पर सीधे पुर को लागू क्यों नहीं करें?

// header file: 
class Cat { 
    private: 
     class CatImpl; // Not defined here 
     CatImpl *cat_; // Handle 

    public: 
     Cat();   // Constructor 
     ~Cat();   // Destructor 
     // Other operations... 
     Purr(); 
}; 


// CPP file: 
#include "cat.h" 

class Cat::CatImpl { 
    Purr(); 
...  // The actual implementation can be anything 
}; 

Cat::Cat() { 
    cat_ = new CatImpl; 
} 

Cat::~Cat() { 
    delete cat_; 
} 

Cat::Purr(){ cat_->Purr(); } 
CatImpl::Purr(){ 
    printf("purrrrrr"); 
} 
+26

क्योंकि दलाल मुहावरा से बचा जाना चाहिए .. – mlvljr

+1

बहुत बढ़िया जवाब, और मैं इस लिंक मिल गया के रूप में अच्छी तरह से व्यापक जानकारी होती है: http://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl/ – zhanxw

+11

हैं आप रखरखाव कोडर को एक पक्ष के बाद आना चाहते हैं, याद रखें कि यह एक ** इंटरफ़ेस ** पैटर्न है। हर आंतरिक वर्ग के लिए इसका इस्तेमाल न करें। ब्लेड रनर को उद्धृत करने के लिए, मैंने देखा है *** आप लोग विश्वास नहीं करेंगे। – DevSolar

उत्तर

34
  • क्योंकि (विशेष रूप से लंबे समय के निर्माण से बचने में) आप के निजी सदस्यों का उपयोग करने में सक्षम होने के लिए Purr() चाहते हैं। Cat::Purr() को friend घोषणा के बिना ऐसी पहुंच की अनुमति नहीं दी जाएगी।
  • क्योंकि आप जिम्मेदारियों को मिश्रण नहीं करते हैं: एक वर्ग के उपकरण, एक वर्ग आगे।
+4

हालांकि यह बनाए रखने के लिए दर्द है। लेकिन फिर फिर, यदि यह एक पुस्तकालय वर्ग है तो विधियों को वैसे भी बदला नहीं जाना चाहिए। मैं जो कोड देख रहा हूं वह सुरक्षित सड़क ले रहा है और हर जगह पिंपल का उपयोग कर रहा है। – JeffV

+0

यह लाइन अवैध नहीं है क्योंकि सभी सदस्य निजी हैं: बिल्ली _-> पुर्र(); पुर() बाहर से सुलभ नहीं है क्योंकि बहरे द्वारा यह निजी है। मुझे यहां क्या समझ नहीं आ रहा है? – binaryguy

+0

दोनों अंक कोई समझ नहीं आता है। यदि आपके पास एक वर्ग था - 'बिल्ली' यह अपने सदस्यों तक पहुंचने में भी सक्षम होगा और यह "प्रतिशोधों को मिश्रण नहीं करेगा", क्योंकि यह वह वर्ग होगा जो "लागू" होगा। पीआईएमपीएल का उपयोग करने के कारण अलग हैं। – doc

2

impl-> म्याऊँ अंदर cpp फाइल करने के लिए कॉल करने का मतलब है कि भविष्य में आप कुछ हेडर फाइल को बदलने के लिए बिना पूरी तरह से अलग कर सकता है कि। हो सकता है कि अगले वर्ष वे एक सहायक विधि खोज लें जिसे वे इसके बजाय बुला सकते थे और इसलिए वे कोड को सीधे कॉल करने के लिए बदल सकते हैं और impl-> Purr का उपयोग नहीं कर सकते हैं। (हां, वे वास्तविक इंप्रेशन :: पुरानी विधि को अपडेट करके भी वही चीज़ प्राप्त कर सकते हैं, लेकिन उस स्थिति में आप एक अतिरिक्त फ़ंक्शन कॉल के साथ फंस गए हैं जो अगले कार्य को बदले में कुछ भी नहीं प्राप्त करता है)

इसका भी अर्थ है शीर्षलेख में केवल परिभाषाएं होती हैं और इसमें कोई कार्यान्वयन नहीं होता है जो क्लीनर अलगाव के लिए बनाता है, जो मुहावरे का पूरा बिंदु है।

50

मुझे लगता है कि ज्यादातर लोग हैंडल बॉडी मुहावरे के रूप में इसका उल्लेख करते हैं। जेम्स कॉप्लिएन की पुस्तक एडवांस्ड सी ++ प्रोग्रामिंग स्टाइल और इडियम्स (Amazon link) देखें। इसे लुईस कैरोल के चरित्र की वजह से Cheshire Cat के रूप में भी जाना जाता है जो केवल ग्रीन अवशेष तक दूर हो जाता है।

उदाहरण कोड स्रोत फ़ाइलों के दो सेटों में वितरित किया जाना चाहिए। फिर केवल Cat.h वह फ़ाइल है जिसे उत्पाद के साथ भेज दिया जाता है।

CatImpl.h Cat.cpp और CatImpl.cpp द्वारा शामिल है CatImpl :: Purr() के लिए कार्यान्वयन शामिल है। यह आपके उत्पाद का उपयोग करके जनता के लिए दृश्यमान नहीं होगा।

असल में विचार कार्यान्वयन फॉम प्राइइंग आंखों के जितना संभव हो उतना छिपाना है। यह सबसे उपयोगी है जहां आपके पास एक वाणिज्यिक उत्पाद है जिसे लाइब्रेरीज़ की एक श्रृंखला के रूप में भेजा जाता है जिसे एपीआई के माध्यम से एक्सेस किया जाता है जिसे ग्राहक के कोड के साथ संकलित और लिंक किया जाता है।

हम दूसरों के द्वारा उल्लेख किया है, का उपयोग करते हुए उनकी तकनीक पूरी तरह से वस्तु के इंटरफ़ेस से कार्यान्वयन अलग करता 2000

में IONAs Orbix 3.3 उत्पाद का पुनर्लेखन के साथ ऐसा किया। तो अगर आप Purr() के कार्यान्वयन को बदलना चाहते हैं तो आपको बिल्ली का उपयोग करने वाली हर चीज को फिर से कंपाइल करना होगा।

इस तकनीक का उपयोग design by contract नामक एक पद्धति में किया जाता है।

+4

पिंपल मुहावरे (या हैंडल-बॉडी मुहावरे जिसे आप इसे कहते हैं) अनुबंध द्वारा डिज़ाइन में उपयोग किया जाता है? –

+0

हाय एंड्रियास, एपीआई उपयोगकर्ता के लिए इंटरफ़ेस का आपका बिंदु केवल उजागर अनुबंध (हैंडल) में है और जिस तरह से आप विज्ञापन को कार्यक्षमता प्रदान करने के लिए शरीर को लागू करते हैं। जब आप फिट देखते हैं, तो आप कार्यान्वयन को बदलने के लिए स्वतंत्र हैं, बशर्ते कि आपने एपीआई के अर्थशास्त्र को नहीं बदला है जिसे आपने विज्ञापित किया है। –

+0

@ रोब, मैं इस पर बहुत कम ओवरहेड अनुमान लगा रहा हूं। एक अतिरिक्त कक्षा, लेकिन उनके लिए बहुत कम है। मौजूदा वर्ग के चारों ओर बस एक पतली आवरण। अगर मैं गलत हूं तो कोई मुझे सही करेगा, लेकिन मेमोरी उपयोग रैम में एक अतिरिक्त फ़ंक्शन टेबल, पिंपल के लिए एक पॉइंटर और कोडस्पेस में प्रत्येक विधि के लिए रीडायरेक्टिंग फ़ंक्शन होगा। हालांकि रखरखाव और डिबगिंग के लिए दर्दनाक। – JeffV

6

आमतौर पर, स्वामी वर्ग (इस मामले में बिल्ली) के लिए हेडर में पिंपल क्लास का एकमात्र संदर्भ एक आगे की घोषणा होगी, जैसा कि आपने यहां किया है, क्योंकि इससे निर्भरता बहुत कम हो सकती है।

उदाहरण के लिए, यदि आपके पिंपल क्लास में सदस्य के रूप में जटिल क्लास है (और केवल एक सूचक या इसके संदर्भ में नहीं) तो आपको इसके उपयोग से पहले जटिल क्लास को पूरी तरह से परिभाषित करने की आवश्यकता होगी। अभ्यास में, इसका मतलब है "जटिल क्लास.h" (जो अप्रत्यक्ष रूप से कुछ भी शामिल करेगा जटिल क्लास पर निर्भर करता है)। यह बहुत से सामानों में खींचने के लिए एक ही हेडर भर सकता है और बहुत सारी चीज़ें, जो आपकी निर्भरताओं (और आपके संकलन के समय) के प्रबंधन के लिए खराब है।

जब आप पिंपल आइडियन का उपयोग करते हैं, तो आपको केवल अपने मालिक के प्रकार के सार्वजनिक इंटरफ़ेस में उपयोग की जाने वाली सामग्री को शामिल करना होगा (जो यहां बिल्ली होगा)। जो आपकी लाइब्रेरी का उपयोग करने वाले लोगों के लिए चीजों को बेहतर बनाता है, और इसका मतलब है कि आपको अपनी लाइब्रेरी के कुछ आंतरिक भाग के आधार पर लोगों के बारे में चिंता करने की आवश्यकता नहीं है - या तो गलती से, या क्योंकि वे ऐसा कुछ करना चाहते हैं जिसे आप अनुमति नहीं देते हैं तो वे # परिभाषित करें अपनी फाइलों को शामिल करने से पहले निजी सार्वजनिक।

यदि यह एक साधारण क्लास है, वहां आम तौर पर एक Pimpl उपयोग करने के लिए कोई कारण नहीं है, लेकिन कई बार के लिए जब प्रकार काफी बड़े हैं, यह एक भारी सहायता कर सकता

14

यदि आपकी कक्षा पिंपल मुहावरे का उपयोग करती है, तो आप सार्वजनिक वर्ग पर हेडर फ़ाइल को बदलने से बच सकते हैं।

यह आपको बाहरी वर्ग की शीर्षलेख फ़ाइल को संशोधित किए बिना, पिंपल क्लास में विधियों को जोड़ने/निकालने की अनुमति देता है। आप Pimpl में # शामिल भी जोड़/निकाल सकते हैं।

जब आप बाहरी वर्ग के हेडर फाइल बदलते हैं, तो सब कुछ है कि #includes पुन: संयोजित करने के लिए है यह (और यदि वे की किसी भी फाइल हेडर रहे हैं, आप सब कुछ है कि उन्हें #includes, और इतने पर पुन: संयोजित करने के लिए है)

0

अगर यह एक अंतर के लायक उल्लेख लेकिन है मैं नहीं जानता ...

यह अपने आप ही नाम स्थान में प्रवर्तन और उपयोगकर्ता देखता कोड के लिए एक सार्वजनिक आवरण/पुस्तकालय नाम स्थान के लिए संभव है:

catlib::Cat::Purr(){ cat_->Purr(); } 
cat::Cat::Purr(){ 
    printf("purrrrrr"); 
} 

इस तरह सभी lib रैरी कोड बिल्ली नामस्थान का उपयोग कर सकता है और उपयोगकर्ता को कक्षा को बेनकाब करने की आवश्यकता के रूप में catlib नामस्थान में एक रैपर बनाया जा सकता है।

0

मुझे यह कह रहा है कि, पिंपल मुहावरे कितनी अच्छी तरह से जाना जाता है, मुझे वास्तविक जीवन में अक्सर फसल बार नहीं दिखता है (उदा। ओपन सोर्स प्रोजेक्ट्स में)।

मुझे अक्सर आश्चर्य होता है कि "लाभ" खत्म हो गए हैं; हां, आप अपने कुछ कार्यान्वयन विवरणों को और भी छुपा सकते हैं, और हां, आप हेडर को बदलने के बिना अपना कार्यान्वयन बदल सकते हैं, लेकिन यह स्पष्ट नहीं है कि ये वास्तविकता में बड़े फायदे हैं।

यह कहना है कि यह स्पष्ट नहीं है कि अच्छी तरह छिपी हुई है, और शायद यह दुर्लभ है कि लोग वास्तव में केवल कार्यान्वयन को बदलते हैं; जैसे ही आपको नई विधियों को जोड़ने की आवश्यकता है, कहें, आपको वैसे भी हेडर बदलने की जरूरत है।

+1

हां, यह केवल एक फर्क पड़ता है यदि आप एक अच्छा सार्वजनिक इंटरफ़ेस चुन सकते हैं और इसके साथ चिपके रह सकते हैं। चूंकि रॉब वेल्स का उल्लेख है, यह महत्वपूर्ण है कि आपको अपनी लाइब्रेरी के संकलित संस्करणों के साथ जुड़े लोगों को अद्यतन पुस्तकालयों को वितरित करने की आवश्यकता हो, बिना उन्हें पुन: संकलित करने के लिए मजबूर किए - आप बस एक नया डीएलएल और वॉयला प्रदान करते हैं। इंटरफेस (== सी ++ में डेटा सदस्यों के बिना सार तत्वों) का उपयोग करके आपको याद रखें, कम रखरखाव के साथ बहुत कुछ प्राप्त करता है (प्रत्येक सार्वजनिक विधि को मैन्युअल रूप से आगे बढ़ाने की आवश्यकता नहीं है)। लेकिन ओटीओएच आपको उदाहरण बनाने के लिए विशेष वाक्यविन्यास का उपयोग करना होगा (यानी एक फैक्ट्री विधि का आह्वान करें)। –

+4

क्यूटी पीआईएमपीएल मुहावरे का व्यापक रूप से उपयोग करता है। –

1

मैं सिर्फ पिछले कुछ दिनों से अधिक मेरी पहली pimpl वर्ग को लागू किया। मैंने बोर्लैंड बिल्डर में winsock2.h सहित समस्याओं को दूर करने के लिए इसका इस्तेमाल किया। ऐसा लगता है कि संरचना संरेखण खराब हो रहा था और चूंकि मेरे पास क्लास निजी डेटा में सॉकेट चीजें थीं, इसलिए वे समस्याएं किसी भी सीपीपी फ़ाइल में फैल रही थीं जिसमें हेडर शामिल था।

pimpl का उपयोग करके, winsock2.h केवल एक cpp फ़ाइल में जहाँ मैं और समस्या पर एक ढक्कन डाल नहीं कर सकता चिंता है कि वह वापस आ मुझे काट करने के लिए शामिल किया गया था।

मूल प्रश्न का उत्तर देने के लिए, पिंपल क्लास को कॉल अग्रेषित करने में मुझे जो लाभ मिला, वह यह था कि पिंपल क्लास वही है जो आपकी मूल कक्षा आपके द्वारा पिंपल करने से पहले होती थी, साथ ही आपके कार्यान्वयन ' कुछ अजीब फैशन में 2 कक्षाओं में फैल गया। प्रकाशन को लागू करने के लिए यह बहुत स्पष्ट है कि बस पिंपल कक्षा में आगे बढ़ें।

श्री Nodet की तरह कहा, एक वर्ग, एक जिम्मेदारी है।

3

ठीक है, मैं इसका इस्तेमाल नहीं होता। मैं एक बेहतर विकल्प है:

foo.h:

class Foo { 
public: 
    virtual ~Foo() { } 
    virtual void someMethod() = 0; 

    // This "replaces" the constructor 
    static Foo *create(); 
} 

foo.cpp:

namespace { 
    class FooImpl: virtual public Foo { 

    public: 
     void someMethod() { 
      //.... 
     }  
    }; 
} 

Foo *Foo::create() { 
    return new FooImpl; 
} 

इस पैटर्न एक नाम है?

एक भी अजगर और जावा प्रोग्रामर के रूप में, मैं pimpl मुहावरा की तुलना में इस एक बहुत अधिक पसंद है।

+2

यदि आप स्वयं को ऑब्जेक्ट बनाने के लिए फैक्ट्री दृष्टिकोण पर सीमित कर रहे हैं, तो यह ठीक है। लेकिन यह मूल्य semantics पूरी तरह से समाप्त करता है, जबकि एक पारंपरिक pImpl या तो विधि के साथ काम करता है। –

+2

अच्छी तरह से pImpl मूल रूप से सिर्फ सूचक लपेटता है। आपको बस इतना करना है कि उपरोक्त() को एक पॉइंटरवापर विथकॉपीसेमेंटिक्स :-) लौटाएं। मैं आमतौर पर विरोध करता हूं, और एक std :: auto_ptr लौटाता हूं। –

+10

संरचना पर विरासत क्यों? एक vtable के ऊपरी हिस्से क्यों जोड़ें? वर्चुअल विरासत क्यों? – derpface

17

क्या लायक है के लिए, यह इंटरफ़ेस से कार्यान्वयन अलग करती है। यह आमतौर पर छोटे आकार की परियोजनाओं में बहुत महत्वपूर्ण नहीं है। लेकिन, बड़ी परियोजनाओं और पुस्तकालयों में, इसका उपयोग निर्माण के समय को कम करने के लिए किया जा सकता है।

मान लें कि Cat के कार्यान्वयन में कई शीर्षलेख शामिल हो सकते हैं, इसमें टेम्पलेट मेटा-प्रोग्रामिंग शामिल हो सकती है जिसमें स्वयं को संकलित करने में समय लगता है। एक उपयोगकर्ता, जो Cat का उपयोग करना चाहता है, उसे क्यों शामिल करना चाहिए? इसलिए, सभी आवश्यक फाइलें पिंपल मुहावरे (इसलिए CatImpl की अगली घोषणा) का उपयोग करके छिपी हुई हैं, और इंटरफ़ेस का उपयोग करने से उपयोगकर्ता को उन्हें शामिल करने के लिए मजबूर नहीं किया जाता है।

मैं nonlinear अनुकूलन के लिए एक पुस्तकालय विकसित कर रहा हूं ("बहुत बुरा गणित" पढ़ें), जो टेम्पलेट्स में लागू किया गया है, इसलिए अधिकांश कोड हेडर में हैं। संकलन करने के लिए लगभग पांच मिनट लगते हैं (एक सभ्य मल्टी-कोर सीपीयू पर), और अन्यथा खाली .cpp में शीर्षलेखों को पार्स करने में लगभग एक मिनट लगते हैं। इसलिए लाइब्रेरी का उपयोग करने वाले किसी भी व्यक्ति को अपना कोड संकलित करने में हर मिनट कुछ मिनट इंतजार करना पड़ता है, जो विकास को tedious बनाता है। हालांकि, कार्यान्वयन और शीर्षकों को छिपाकर, एक में एक साधारण इंटरफ़ेस फ़ाइल शामिल होती है, जो तुरंत संकलित होती है।

यह अन्य कंपनियों द्वारा प्रतिलिपि बनाने से कार्यान्वयन की रक्षा के साथ कुछ भी करने की ज़रूरत नहीं है - जो संभवतः तब तक नहीं होगा, जब तक कि आपके एल्गोरिदम की आंतरिक कार्यप्रणाली सदस्य चर की परिभाषाओं से अनुमानित न हो (यदि इसलिए, यह शायद बहुत जटिल नहीं है और पहली जगह में सुरक्षा के लायक नहीं है)।

1

हम पहलू उन्मुख प्रोग्रामिंग का अनुकरण करने के लिए पीआईएमपीएल मुहावरे का उपयोग करते हैं जहां पूर्व, पोस्ट और त्रुटि पहलुओं को सदस्य फ़ंक्शन के निष्पादन से पहले और बाद में बुलाया जाता है।

struct Omg{ 
    void purr(){ cout<< "purr\n"; } 
}; 

struct Lol{ 
    Omg* omg; 
    /*...*/ 
    void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } } 
}; 

हम कई वर्गों के बीच विभिन्न पहलुओं को साझा करने के लिए बेस क्लास के पॉइंटर का भी उपयोग करते हैं।

इस दृष्टिकोण की कमी यह है कि लाइब्रेरी उपयोगकर्ता को उन सभी पहलुओं को ध्यान में रखना होगा जिन्हें निष्पादित किया जा रहा है, लेकिन केवल उनकी कक्षा को देखता है। इसे किसी भी दुष्प्रभाव के लिए दस्तावेज़ ब्राउज़ करने की आवश्यकता है। ?

संबंधित मुद्दे