PImpl पैटर्न का क्या मतलब है जबकि हम C ++ में समान उद्देश्य के लिए इंटरफ़ेस का उपयोग कर सकते हैं?


49

मुझे बहुत सारे सोर्स कोड दिखाई दे रहे हैं जो C ++ में PImpl मुहावरे का उपयोग कर रहे हैं। मुझे लगता है कि इसका उद्देश्य निजी डेटा / प्रकार / कार्यान्वयन को छिपाना है, इसलिए यह निर्भरता को दूर कर सकता है, और फिर संकलन समय और शीर्षक को कम कर सकता है।

लेकिन C ++ में इंटरफ़ेस / शुद्ध-सार कक्षाएं भी यह क्षमता रखती हैं, उनका उपयोग डेटा / प्रकार / कार्यान्वयन को छिपाने के लिए भी किया जा सकता है। और कॉल करने वाले को केवल ऑब्जेक्ट बनाते समय इंटरफ़ेस को देखने के लिए, हम इंटरफ़ेस के हेडर में फ़ैक्टरी विधि की घोषणा कर सकते हैं।

तुलना है:

  1. लागत :

    इंटरफ़ेस तरीका लागत कम है, क्योंकि आपको सार्वजनिक आवरण फ़ंक्शन कार्यान्वयन को दोहराने की आवश्यकता नहीं है void Bar::doWork() { return m_impl->doWork(); }, आपको बस इंटरफ़ेस में हस्ताक्षर को परिभाषित करने की आवश्यकता है।

  2. अच्छी तरह से समझा :

    इंटरफ़ेस तकनीक को हर C ++ डेवलपर द्वारा बेहतर ढंग से समझा जाता है।

  3. प्रदर्शन :

    इंटरफ़ेस तरीका प्रदर्शन PImpl मुहावरे से भी बदतर नहीं है, दोनों एक अतिरिक्त मेमोरी एक्सेस। मुझे लगता है कि प्रदर्शन समान है।

निम्नलिखित मेरे प्रश्न को स्पष्ट करने के लिए छद्मकोश है:

// Forward declaration can help you avoid include BarImpl header, and those included in BarImpl header.
class BarImpl;
class Bar
{
public:
    // public functions
    void doWork();
private:
    // You don't need to compile Bar.cpp after changing the implementation in BarImpl.cpp
    BarImpl* m_impl;
};

एक ही उद्देश्य इंटरफ़ेस का उपयोग करके लागू किया जा सकता है:

// Bar.h
class IBar
{
public:
    virtual ~IBar(){}
    // public functions
    virtual void doWork() = 0;
};

// to only expose the interface instead of class name to caller
IBar* createObject();

तो PImpl का क्या मतलब है?

जवाबों:


50

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

PImpl का उपयोग करने के तीन कारण हैं:

  1. बाइनरी इंटरफ़ेस (ABI) को निजी सदस्यों से स्वतंत्र बनाना । आश्रित कोड को फिर से जमा किए बिना एक साझा पुस्तकालय को अपडेट करना संभव है, लेकिन जब तक बाइनरी इंटरफ़ेस समान रहता है। अब गैर-सदस्य फ़ंक्शन को जोड़ने और गैर-वर्चुअल सदस्य फ़ंक्शन को जोड़ने के अलावा, हेडर में लगभग कोई भी परिवर्तन, एबीआई को बदल देता है। PImpl मुहावरा निजी सदस्यों की परिभाषा को स्रोत में बदल देता है और इस प्रकार ABI को उनकी परिभाषा से अलग करता है। Fragile बाइनरी इंटरफ़ेस समस्या देखें

  2. जब कोई हेडर बदलता है, तो उसके सहित सभी स्रोतों को फिर से तैयार करना पड़ता है। और C ++ संकलन धीमा है। इसलिए स्रोत में निजी सदस्यों की परिभाषाओं को स्थानांतरित करके, PImpl मुहावरा संकलन समय को कम कर देता है, क्योंकि हेडर में कम निर्भरता को खींचने की आवश्यकता होती है, और संशोधनों के बाद संकलन समय को और भी कम कर देता है क्योंकि आश्रितों को फिर से जोड़ने की आवश्यकता नहीं होती है। (ठीक है, यह छिपे हुए ठोस वर्ग के साथ इंटरफ़ेस + फ़ैक्टरी फ़ंक्शन पर भी लागू होता है)।

  3. C ++ में कई वर्गों के लिए अपवाद सुरक्षा एक महत्वपूर्ण संपत्ति है। अक्सर आपको कई वर्गों को एक में लिखने की आवश्यकता होती है ताकि यदि एक से अधिक सदस्य थ्रो पर ऑपरेशन के दौरान, कोई भी सदस्य संशोधित न हो या आपके पास ऐसा ऑपरेशन हो जो सदस्य को असंगत अवस्था में छोड़ दे यदि वह फेंकता है और आपको बने रहने के लिए संबंधित ऑब्जेक्ट की आवश्यकता होती है संगत। ऐसे मामले में आप PImpl की नई आवृत्ति बनाकर ऑपरेशन को कार्यान्वित करते हैं और ऑपरेशन सफल होने पर उन्हें स्वैप करते हैं।

वास्तव में इंटरफ़ेस का उपयोग केवल कार्यान्वयन को छिपाने के लिए किया जा सकता है, लेकिन इसके निम्नलिखित नुकसान हैं:

  1. गैर-आभासी पद्धति को जोड़ने से एबीआई नहीं टूटता है, लेकिन एक आभासी जोड़ने से यह होता है। इसलिए इंटरफेस सभी तरीकों को जोड़ने की अनुमति नहीं देते हैं, PImpl करता है।

  2. इंट्रोडक्शन का उपयोग केवल पॉइंटर / संदर्भ के माध्यम से किया जा सकता है, इसलिए उपयोगकर्ता को उचित संसाधन प्रबंधन का ध्यान रखना होगा। दूसरी ओर PImpl का उपयोग करने वाली कक्षाएं अभी भी मूल्य प्रकार हैं और आंतरिक रूप से संसाधनों को संभालती हैं।

  3. छिपे हुए कार्यान्वयन को विरासत में नहीं लिया जा सकता है, PImpl के साथ वर्ग।

और निश्चित रूप से इंटरफ़ेस अपवाद सुरक्षा के साथ मदद नहीं करेगा। आपको उसके लिए कक्षा के अंदर अप्रत्यक्षता की आवश्यकता है।


अच्छी बात। सार्वजनिक फ़ंक्शन जोड़ें Implक्लास में नहीं टूटेगा ABI, लेकिन इंटरफ़ेस में सार्वजनिक फ़ंक्शन जोड़ें टूट जाएगा ABI
१२:

1
एक सार्वजनिक समारोह / विधि जोड़ना ABI को तोड़ना नहीं है ; कड़ाई से additive परिवर्तन परिवर्तन नहीं तोड़ रहे हैं। हालाँकि, आपको कभी इतना सावधान रहना होगा; फ्रंट-एंड और बैक-एंड के बीच मध्यस्थता करने वाले कोड को वर्जनिंग के साथ सभी प्रकार के मज़े से निपटना पड़ता है। यह सब मुश्किल हो जाता है। (इस तरह की बात यह है कि क्यों कई सॉफ्टवेयर प्रोजेक्ट्स C से C ++ को पसंद करते हैं; यह उन्हें ABI के बारे में ठीक-ठीक जानकारी देता है।)
डोनल फेलो

3
@DonalFellows: सार्वजनिक फ़ंक्शन / विधि जोड़ने से ABI नहीं टूटता है। वर्चुअल विधि को जोड़ना और ऐसा हमेशा करता है जब तक कि उपयोगकर्ता को ऑब्जेक्ट को इनहेरिट करने से रोका नहीं जाता है (जो PImpl प्राप्त करता है)।
Jan Hudec

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

1
PImpl प्रारंभिक संकलन समय को भी कम करता है। उदाहरण के लिए, मेरी कार्यान्वयन कुछ टेम्पलेट प्रकार का एक क्षेत्र है, तो (उदाहरण के लिए unordered_set), pimpl के बिना, मैं करने की जरूरत #include <unordered_set>में MyClass.h। PImpl के साथ, मुझे केवल इसे .cppफ़ाइल में शामिल करने की आवश्यकता है , न कि .hफ़ाइल में, और इसलिए बाकी सब कुछ जिसमें यह शामिल है ...
मार्टिन सी। मार्टिन

7

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

यदि आपके PIMPL वर्ग का प्रदर्शन महत्वपूर्ण तरीके से नहीं किया जाता है, तो यह बिंदु ज्यादा मायने नहीं रखता है, लेकिन आपकी धारणा यह है कि प्रदर्शन केवल कुछ स्थितियों में ही होता है, सभी में नहीं।


1
लिंक-टाइम ऑप्टिमाइज़ेशन के उपयोग के साथ, pimpl मुहावरे का उपयोग करने के अधिकांश ओवरहेड को हटा दिया जाता है। दोनों बाहरी आवरण कार्यों के साथ-साथ कार्यान्वयन कार्यों को भी रेखांकित किया जा सकता है, जिससे फ़ंक्शन कॉल प्रभावी रूप से हेडर के अंदर लागू सामान्य वर्ग की तरह हो सकता है।
goji

यह केवल सच है अगर आप लिंक-टाइम ऑप्टिमाइज़ेशन का उपयोग करते हैं। PImpl की बात यह है कि कंपाइलर का कार्यान्वयन नहीं है, अग्रेषण फ़ंक्शन का भी नहीं। लिंकर हालांकि करता है।
मार्टिन सी। मार्टिन

5

यह पृष्ठ आपके प्रश्न का उत्तर देता है। बहुत से लोग आपके दावे से सहमत हैं। Pimpl का उपयोग करने से निजी क्षेत्रों / सदस्यों पर निम्नलिखित लाभ होते हैं।

  1. किसी वर्ग के निजी सदस्य चर को बदलने के लिए उस पर निर्भर वर्गों को फिर से जमा करने की आवश्यकता नहीं होती है, इस प्रकार यह समय अधिक तेज़ हो जाता है और FragileBinaryInterfaceProblem कम हो जाता है।
  2. हेडर फ़ाइल को निजी सदस्यों के चर में 'वैल्यू' द्वारा उपयोग की जाने वाली #include कक्षाओं की आवश्यकता नहीं होती है, इस प्रकार संकलन समय अधिक तेज़ होता है।

पिंपल मुहावरा एक संकलन समय अनुकूलन, एक निर्भरता तोड़ने की तकनीक है। यह बड़ी हेडर फ़ाइलों को काट देता है। यह आपके शीर्ष लेख को केवल सार्वजनिक इंटरफ़ेस तक कम करता है। सी ++ में हेडर की लंबाई महत्वपूर्ण है जब आप सोचते हैं कि यह कैसे काम करता है। #include हेडर फ़ाइल को स्रोत फ़ाइल में प्रभावी रूप से सम्मिलित करता है - इसलिए पूर्व-संसाधित C ++ इकाइयों के बाद ध्यान में रखना बहुत बड़ी हो सकती है। Pimpl का उपयोग करने से संकलन समय में सुधार हो सकता है।

अन्य बेहतर निर्भरता ब्रेकिंग तकनीक हैं - जिसमें आपके डिज़ाइन को बेहतर बनाना शामिल है। निजी तरीके क्या दर्शाते हैं? एकल जिम्मेदारी क्या है? क्या उन्हें एक और वर्ग होना चाहिए?


PImpl रन-टाइम सम्मान में एक अनुकूलन नहीं है। आवंटन तुच्छ नहीं हैं और दाना का मतलब अतिरिक्त आवंटन है। यह एक निर्भरता-ब्रेकिंग तकनीक है और केवल संकलन समय का अनुकूलन है ।
Jan Hudec

@ जानहुडेक ने स्पष्ट किया।
डेव हिलियर

@DaveHillier, +1 उल्लेख FragileBinaryInterfaceProblem के लिए
ZijingWu
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.