आप C ++ में इंटरफ़ेस कैसे घोषित करते हैं?


804

मैं एक इंटरफ़ेस का प्रतिनिधित्व करने वाले वर्ग को कैसे सेटअप करूं? क्या यह सिर्फ एक सार आधार वर्ग है?


के संभावित डुप्लिकेट stackoverflow.com/questions/318064/...
अशक्त

जवाबों:


685

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

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

आपको वर्चुअल डिस्ट्रक्टर के लिए एक निकाय शामिल करने की आवश्यकता नहीं है - यह पता चलता है कि कुछ कंपाइलरों को एक खाली डिस्ट्रॉक्टर को अनुकूलित करने में परेशानी होती है और आप डिफ़ॉल्ट का उपयोग करना बेहतर समझते हैं।


105
वर्चुअल डेसक्टेक्टर ++! यह बहुत महत्वपूर्ण है। आप संचालक की शुद्ध आभासी घोषणाओं को भी शामिल करना चाह सकते हैं = और आपके लिए कंपाइलर ऑटो-जनरेटिंग को रोकने के लिए कंस्ट्रक्टर की परिभाषाओं को कॉपी कर सकते हैं।
एक्सन

33
एक आभासी विध्वंसक का एक विकल्प एक संरक्षित विध्वंसक है। यह बहुरूपी विनाश को निष्क्रिय करता है, जो कुछ परिस्थितियों में अधिक उपयुक्त हो सकता है। Gotw.ca/publications/mill18.htm में "दिशानिर्देश # 4" देखें
फ्रेड लार्सन

9
एक अन्य विकल्प =0एक शरीर के साथ शुद्ध आभासी ( ) विध्वंसक को परिभाषित करना है। यहाँ लाभ यह है कि संकलक, सैद्धांतिक रूप से, यह देख सकता है कि व्यवहार्य अब कोई वैध सदस्य नहीं है, और इसे पूरी तरह से छोड़ दें। एक शरीर के साथ एक आभासी विध्वंसक के साथ, कहा जाता है कि विध्वंसक कहा जा सकता है (वस्तुतः) thisसूचक के माध्यम से निर्माण के बीच में (जब निर्मित वस्तु अभी भी Parentप्रकार की है), और इसलिए संकलक को एक वैध व्यवहार्यता प्रदान करनी है। इसलिए यदि आप thisनिर्माण के दौरान स्पष्ट रूप से वर्चुअल डिस्ट्रक्टर्स को नहीं बुलाते हैं :) आप कोड आकार पर बचत कर सकते हैं।
पावेल मिनाएव

50
C ++ का कितना विशिष्ट उत्तर है कि शीर्ष उत्तर सीधे प्रश्न का उत्तर नहीं देता (हालांकि स्पष्ट रूप से कोड सही है), इसके बजाय यह सरल उत्तर का अनुकूलन करता है।
टिम

18
यह मत भूलो कि C ++ 11 में आप overrideसंकलन-समय तर्क के लिए अनुमति देने के लिए कीवर्ड निर्दिष्ट कर सकते हैं और मूल्य प्रकार की जाँच वापस कर सकते हैं । उदाहरण के लिए, बाल की घोषणा मेंvirtual void OverrideMe() override;
शॉन

243

शुद्ध आभासी तरीकों के साथ एक कक्षा बनाएं। एक और वर्ग बनाकर इंटरफ़ेस का उपयोग करें जो उन आभासी तरीकों को ओवरराइड करता है।

एक शुद्ध आभासी विधि एक वर्ग विधि है जिसे आभासी के रूप में परिभाषित किया गया है और 0 को सौंपा गया है।

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

29
आपके पास IDemo में कुछ भी विध्वंसक नहीं होना चाहिए ताकि यह करने के लिए परिभाषित व्यवहार हो: IDemo * p = new Child; / * जो कुछ भी * / हटाएँ p;
इवान टेरान

11
बाल वर्ग में ओवरराइडम विधि क्यों आभासी है? क्या यह जरूरी है?
केमर्रे

9
@Cemre - नहीं, यह आवश्यक नहीं है, लेकिन यह या तो चोट नहीं करता है।
PowerApp101

11
जब भी किसी वर्चुअल विधि को ओवरराइड किया जाता है, तो आमतौर पर कीवर्ड को 'वर्चुअल' रखना एक अच्छा विचार है। हालांकि आवश्यक नहीं है, यह कोड को स्पष्ट कर सकता है - अन्यथा, आपके पास कोई संकेत नहीं है कि उस पद्धति का उपयोग पॉलीमॉर्फिक रूप से किया जा सकता है, या यहां तक ​​कि आधार वर्ग में भी मौजूद है।
केविन

27
@ केविन overrideसी ++ 11 के साथ
कीसर

146

संपूर्ण कारण आपके पास C # / Java में अमूर्त आधार वर्गों के अलावा एक विशेष इंटरफ़ेस प्रकार-श्रेणी है क्योंकि C # / Java एकाधिक वंशानुक्रम का समर्थन नहीं करता है।

C ++ कई उत्तराधिकार का समर्थन करता है, और इसलिए एक विशेष प्रकार की आवश्यकता नहीं है। एक गैर-सार (शुद्ध आभासी) विधियों के साथ एक सार आधार वर्ग कार्यात्मक रूप से C # / Java इंटरफ़ेस के बराबर है।


17
अभी भी इंटरफेस बनाने में सक्षम होने के लिए अच्छा होगा, हमें इतना टाइप करने से बचाने के लिए (आभासी, = 0, आभासी विध्वंसक)। इसके अलावा कई विरासत मुझे एक बहुत बुरा विचार लगता है और मैंने इसे अभ्यास में कभी नहीं देखा है, लेकिन हर समय इंटरफेस की आवश्यकता होती है। C ++ की खराबता के लिए इंटरफेस केवल इसलिए नहीं शुरू करेगा क्योंकि मैं उन्हें चाहता हूं।
Ha11owed

9
Ha11owed: इसमें इंटरफेस हैं। वे शुद्ध आभासी तरीकों और कोई विधि कार्यान्वयन के साथ कक्षाएं कहते हैं।
मील्स राउत

6
@doc: java.lang.Thread में वे विधियाँ और स्थिरांक हैं जिन्हें आप संभवतः अपनी वस्तु में नहीं रखना चाहते हैं। यदि आप थ्रेड से विस्तार करते हैं, और सार्वजनिक विधि के साथ एक अन्य वर्ग के साथ कंपाइलर को क्या करना चाहिए? क्या आप वास्तव में C ++ की तरह दृढ़ता से नामित आधार बिंदुओं का उपयोग करना पसंद करेंगे? यह खराब डिजाइन की तरह लगता है, आपको आमतौर पर रचना की आवश्यकता होती है जहां आपको लगता है कि आपको कई विरासत की आवश्यकता है।
Ha11owed

4
@ Ha11ged यह बहुत समय पहले था इसलिए मुझे विवरण याद नहीं है, लेकिन इसके तरीके और प्रतियोगी थे जो मैं अपनी कक्षा में रखना चाहता था और इससे भी महत्वपूर्ण बात यह है कि मैं चाहता था कि मेरी व्युत्पन्न वर्ग वस्तु एक Threadउदाहरण हो। मल्टीपल इनहेरिटेंस खराब डिज़ाइन के साथ-साथ कंपोज़िशन भी हो सकती है। यह सब मामले पर निर्भर करता है।
डॉक्टर

2
@ क्या: सच में? उद्देश्य-सी में संकलन-समय मूल्यांकन, और टेम्पलेट हैं?
डिडुप्लिकेटर

51

सी ++ में प्रति सेगमेंट "इंटरफ़ेस" की कोई अवधारणा नहीं है। AFAIK, इंटरफेस को पहली बार जावा में कई वंशानुक्रम की कमी के आसपास काम करने के लिए पेश किया गया था। यह अवधारणा काफी उपयोगी है, और एक सार आधार वर्ग का उपयोग करके C ++ में समान प्रभाव प्राप्त किया जा सकता है।

सार आधार वर्ग एक ऐसा वर्ग है जिसमें कम से कम एक सदस्य फ़ंक्शन (जावा लिंगो में विधि) एक शुद्ध आभासी फ़ंक्शन है जो निम्नलिखित वाक्यविन्यास का उपयोग करके घोषित किया गया है:

class A
{
  virtual void foo() = 0;
};

एक सार आधार वर्ग को तत्काल नहीं किया जा सकता है, अर्थात आप वर्ग ए की एक वस्तु की घोषणा नहीं कर सकते हैं। आप केवल ए से कक्षाएं प्राप्त कर सकते हैं, लेकिन किसी भी व्युत्पन्न वर्ग जो foo()इच्छाशक्ति का कार्यान्वयन प्रदान नहीं करते हैं, वह भी सार होगा। अमूर्त होने से रोकने के लिए, एक व्युत्पन्न वर्ग को उन सभी शुद्ध आभासी कार्यों के लिए कार्यान्वयन प्रदान करना होगा जो उसे विरासत में मिले हैं।

ध्यान दें कि एक सार आधार वर्ग एक इंटरफ़ेस से अधिक हो सकता है, क्योंकि इसमें डेटा सदस्य और सदस्य फ़ंक्शन हो सकते हैं जो शुद्ध आभासी नहीं हैं। एक अंतरफलक के बराबर केवल शुद्ध आभासी कार्यों के साथ किसी भी डेटा के बिना एक सार आधार वर्ग होगा।

और, जैसा कि मार्क रैनसम ने बताया, एक सार आधार वर्ग को किसी भी आधार वर्ग की तरह, एक आभासी विध्वंसक प्रदान करना चाहिए।


13
"कई वंशानुक्रम की कमी" से अधिक, मैं कहूँगा कि कई विरासतों को बदलना। जावा को शुरुआत से ही इस तरह से डिज़ाइन किया गया था क्योंकि एकाधिक वंशानुक्रम यह हल करने की तुलना में अधिक समस्याएं पैदा करता है। अच्छा जवाब
ऑस्करराइज

11
ऑस्कर, जो इस बात पर निर्भर करता है कि आप C ++ प्रोग्रामर हैं जिन्होंने जावा सीखा है या इसके विपरीत। :) IMHO, अगर विवेकपूर्ण रूप से उपयोग किया जाता है, जैसे कि C ++ में लगभग कुछ भी, तो एकाधिक वंशानुक्रम समस्याओं का हल करता है। एक "इंटरफ़ेस" अमूर्त आधार वर्ग कई विरासत के बहुत ही विवेकपूर्ण उपयोग का एक उदाहरण है।
दीमा

8
@OscarRyz गलत है। एमआई केवल दुरुपयोग होने पर समस्या पैदा करता है। एमआई के साथ अधिकांश कथित समस्याएं वैकल्पिक डिजाइन (एमआई के बिना) के साथ भी आएंगी। जब लोगों को एमआई के साथ उनके डिजाइन में कोई समस्या होती है, तो यह एमआई की गलती है; अगर उन्हें SI के साथ कोई डिज़ाइन समस्या है, तो यह उनकी अपनी गलती है। "मौत का हीरा" (बार-बार विरासत) एक प्रमुख उदाहरण है। एमआई बशिंग शुद्ध पाखंड नहीं है, लेकिन करीब है।
उत्सुकतापूर्ण

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

2
@MarekStanley, आप सही हो सकते हैं, लेकिन मेरी इच्छा है कि आप एक बेहतर उदाहरण चुनें। मैं इसे एक इंटरफेस के वारिस के रूप में सोचना पसंद करता हूं। C ++ में आप या तो इंटरफ़ेस और कार्यान्वयन दोनों को एक साथ ले सकते हैं (पब्लिक इनहेरिटेंस) या आप केवल कार्यान्वयन (प्राइवेट इनहेरिटेंस) प्राप्त कर सकते हैं। जावा में आपके पास कार्यान्वयन के बिना, बस इंटरफ़ेस को विरासत में लेने का विकल्प है।
दीमा

43

जहां तक ​​मैं परीक्षण कर सकता था, आभासी विध्वंसक जोड़ना बहुत महत्वपूर्ण है। मैं के साथ बनाई गई वस्तुओं का उपयोग कर रहा हूं newऔर नष्ट कर रहा हूं delete

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

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

यदि आप पिछले कोड को बिना चलाते हैं virtual ~IBase() {};, तो आप देखेंगे कि विध्वंसक Tester::~Tester()कभी नहीं कहा जाता है।


3
इस पृष्ठ पर सर्वश्रेष्ठ उत्तर क्योंकि यह व्यावहारिक, संकलन योग्य उदाहरण की आपूर्ति करके बिंदु बनाता है। चीयर्स!
लूमी

1
Testet :: ~ Tester () केवल तभी चलता है जब obj "Tester के साथ घोषित किया जाता है"।
एलेसांद्रो एल।

दरअसल, स्ट्रिंग प्राइवेटटेनम के डिस्ट्रक्टर को बुलाया जाएगा, और मेमोरी में, यह सब वहाँ के लिए आवंटित किया जाएगा। जहां तक ​​रनटाइम का सवाल है, जब किसी वर्ग के सभी ठोस सदस्यों को नष्ट कर दिया जाता है, तो क्लास उदाहरण है। मैंने एक लाइन क्लास के साथ एक समान प्रयोग करने की कोशिश की जिसमें दो बिंदु संरचनाएं थीं और पाया गया कि दोनों संरचनाएं एक डिलीट कॉल पर नष्ट हो गई हैं (या एक घटी हुई फ़ंक्शन से वापस आ गई हैं)। valgrind ने 0 लीक की पुष्टि की।
क्रिस रीड

33

मेरा जवाब मूल रूप से दूसरों के समान है लेकिन मुझे लगता है कि दो अन्य महत्वपूर्ण चीजें हैं:

  1. अपने इंटरफ़ेस में एक आभासी विध्वंसक की घोषणा करें या यदि कोई व्यक्ति किसी प्रकार की वस्तु को हटाने की कोशिश करता है तो अपरिभाषित व्यवहार से बचने के लिए एक संरक्षित गैर-आभासी एक बनाएं IDemo

  2. एकाधिक विरासत के साथ समस्याओं से बचने के लिए आभासी विरासत का उपयोग करें। (जब हम इंटरफेस का उपयोग करते हैं तो कई बार अधिक वंशानुक्रम होता है।)

और अन्य उत्तरों की तरह:

  • शुद्ध आभासी तरीकों के साथ एक कक्षा बनाएं।
  • एक और वर्ग बनाकर इंटरफ़ेस का उपयोग करें जो उन आभासी तरीकों को ओवरराइड करता है।

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }
    

    या

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }
    

    तथा

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    

2
वर्चुअल इनहेरिटेंस की कोई आवश्यकता नहीं है क्योंकि आपके पास इंटरफ़ेस में कोई डेटा सदस्य नहीं हैं।
रोबोकाइड

3
वर्चुअल इनहेरिटेंस तरीकों के साथ-साथ महत्वपूर्ण है। इसके बिना, आप ओवरराइडमाइ () के साथ अस्पष्टता में चलेंगे, भले ही इसका कोई भी उदाहरण शुद्ध आभासी हो (बस इसने खुद कोशिश की थी)।
नार्फ नेविलस

5
@Avishay_ " वर्चुअल इनहेरिटेंस की कोई आवश्यकता नहीं है क्योंकि आपके पास इंटरफ़ेस में कोई डेटा सदस्य नहीं हैं। " गलत है।
जिज्ञासु

ध्यान दें कि वर्चुअल इनहेरिटेंस कुछ gcc वर्जन पर काम नहीं कर सकती है, क्योंकि संस्करण 4.3.3 जो WinAVR 2010 के साथ शिप किया गया है: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontro

-1 गैर-आभासी संरक्षित विध्वंसक होने के लिए, क्षमा करें
वुल्फ

10

C ++ 11 में आप आसानी से विरासत से पूरी तरह बच सकते हैं:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

इस स्थिति में, एक इंटरफ़ेस में संदर्भ शब्दार्थ होता है, अर्थात आपको यह सुनिश्चित करना होगा कि ऑब्जेक्ट इंटरफ़ेस की रूपरेखा तैयार करता है (मूल्य शब्दार्थ के साथ इंटरफेस बनाना भी संभव है)।

इस प्रकार के इंटरफेस में उनके पेशेवरों और विपक्ष हैं:

अंत में, विरासत सॉफ्टवेयर सॉफ्टवेयर डिजाइन में सभी बुराई की जड़ है। में शॉन अभिभावक की मूल्य शब्दार्थ और अवधारणाओं के आधार पर बहुरूपता (अत्यधिक की सिफारिश की, इस तकनीक का बेहतर संस्करण को समझाया गया है) के बाद मामले का अध्ययन किया जाता है:

कहो कि मेरे पास एक एप्लिकेशन है जिसमें मैं MyShapeइंटरफ़ेस का उपयोग करके अपनी आकृतियों के साथ बहुरूपिए व्यवहार करता हूं :

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

अपने आवेदन में, आप YourShapeइंटरफ़ेस का उपयोग करके विभिन्न आकृतियों के साथ ऐसा ही करते हैं :

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

अब कहते हैं कि आप कुछ आकार का उपयोग करना चाहते हैं जो मैंने आपके आवेदन में विकसित किया है। वैचारिक रूप से, हमारी आकृतियों का इंटरफ़ेस समान है, लेकिन मेरे आकार को आपके एप्लिकेशन में काम करने के लिए आपको मेरी आकृतियों को इस प्रकार विस्तारित करना होगा:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

सबसे पहले, मेरी आकृतियों को संशोधित करना संभव नहीं हो सकता है। इसके अलावा, एकाधिक वंशानुक्रम सड़क को स्पेगेटी कोड की ओर ले जाता है (कल्पना करें कि एक तीसरी परियोजना आती है जो TheirShapeइंटरफ़ेस का उपयोग कर रही है ... क्या होगा यदि वे अपने ड्रॉ फ़ंक्शन को भी कहते हैं my_draw?)।

अद्यतन: गैर-विरासत आधारित बहुरूपता के बारे में कुछ नए संदर्भ हैं:


5
टीबीएच विरासत उस सी ++ 11 की तुलना में कहीं अधिक स्पष्ट है, जो एक इंटरफ़ेस होने का दिखावा करता है, लेकिन कुछ असंगत डिजाइनों को बांधने के लिए एक गोंद है। आकार का उदाहरण वास्तविकता से अलग है और Circleवर्ग एक खराब डिजाइन है। आपको Adapterऐसे मामलों में पैटर्न का उपयोग करना चाहिए । क्षमा करें यदि यह थोड़ा कठोर लगेगा, लेकिन Qtविरासत के बारे में निर्णय लेने से पहले कुछ वास्तविक जीवन पुस्तकालय का उपयोग करने का प्रयास करें । वंशानुक्रम जीवन को बहुत आसान बनाता है।
डॉक

2
यह बिल्कुल कठोर नहीं है। वास्तविकता से आकार का उदाहरण कैसे अलग किया जाता है? क्या आप Adapterपैटर्न का उपयोग करके सर्कल को ठीक करने का एक उदाहरण (शायद विचारधारा पर) दे सकते हैं ? मुझे इसके फायदे देखने में दिलचस्पी है।
gnzlbg

ठीक है, मैं इस छोटे से बॉक्स में फिट होने की कोशिश करूंगा। सबसे पहले, आप आमतौर पर अपने काम को सुरक्षित करने के लिए अपना खुद का आवेदन लिखना शुरू करने से पहले "MyShape" जैसे पुस्तकालयों का चयन करते हैं। अन्यथा आप कैसे जान सकते हैं कि Squareपहले से ही वहाँ नहीं है? पूर्वज्ञान? इसलिए यह वास्तविकता से अलग है। और वास्तव में यदि आप "MyShape" लाइब्रेरी पर भरोसा करना चुनते हैं तो आप शुरू से ही इसके इंटरफेस को अपना सकते हैं। आकृतियों के उदाहरणों में कई निरर्थक हैं (जिनमें से एक यह है कि आपके पास दो Circleसंरचनाएं हैं), लेकिन एडेप्टर कुछ इस तरह दिखाई देगा -> ideone.com/UogjWk
doc

2
यह वास्तविकता से अलग नहीं है। जब कंपनी ए कंपनी बी खरीदती है और कंपनी बी के कोडबेस को ए में एकीकृत करना चाहती है, तो आपके पास दो पूरी तरह से स्वतंत्र कोड आधार हैं। कल्पना कीजिए कि प्रत्येक के पास विभिन्न प्रकारों की आकृति पदानुक्रम है। आप उन्हें आसानी से विरासत के साथ जोड़ नहीं सकते हैं, और कंपनी सी को जोड़ सकते हैं और आपके पास एक बड़ी गड़बड़ है। मुझे लगता है कि आपको यह बात देखनी चाहिए: youtube.com/watch?v=0I0FD3N5cgM मेरा उत्तर पुराना है, लेकिन आप समानताएँ देखेंगे। आपको हर समय सबकुछ फिर से लागू करने की आवश्यकता नहीं है, आप इंटरफ़ेस में एक कार्यान्वयन प्रदान कर सकते हैं, और यदि उपलब्ध हो तो एक सदस्य फ़ंक्शन चुनें।
gnzlbg

1
मैंने वीडियो का हिस्सा देखा है और यह पूरी तरह से गलत है। मैं डीबगिंग उद्देश्यों को छोड़कर डायनेमिक_का उपयोग कभी नहीं करता। डायनामिक कास्ट का मतलब है कि आपके डिज़ाइन में कुछ गड़बड़ है और इस वीडियो में डिज़ाइन गलत हैं :)। गाइ ने Qt का भी उल्लेख किया है, लेकिन यहां तक ​​कि वह गलत है - QLayout QWidget से विरासत में नहीं है और न ही दूसरे तरीके से!
doc

9

ऊपर सभी अच्छे जवाब। एक अतिरिक्त चीज जिसे आपको ध्यान में रखना चाहिए - आपके पास एक शुद्ध आभासी विध्वंसक भी हो सकता है। फर्क सिर्फ इतना है कि आपको इसे लागू करने की आवश्यकता है।

उलझन में?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

ऐसा करने का मुख्य कारण यह है कि यदि आप इंटरफ़ेस विधियाँ प्रदान करना चाहते हैं, जैसा कि मेरे पास है, लेकिन उन्हें वैकल्पिक बनाना।

क्लास बनाने के लिए एक इंटरफ़ेस क्लास के लिए शुद्ध वर्चुअल विधि की आवश्यकता होती है, लेकिन आपके सभी वर्चुअल तरीकों में डिफ़ॉल्ट कार्यान्वयन होता है, इसलिए शुद्ध वर्चुअल बनाने के लिए एकमात्र तरीका विध्वंसक होता है।

व्युत्पन्न वर्ग में एक विध्वंसक को फिर से लागू करना कोई बड़ी बात नहीं है - मैं हमेशा अपने व्युत्पन्न वर्गों में एक विध्वंसक, आभासी या नहीं, को फिर से लागू करता हूं।


4
क्यों, ओह क्यों, किसी को भी इस मामले में शुद्ध आभासी बनाना चाहते हैं? इससे क्या लाभ होगा? आप व्युत्पन्न वर्गों पर बस कुछ ऐसा करने के लिए मजबूर करेंगे, जिसमें उन्हें शामिल करने की कोई आवश्यकता नहीं है - एक डॉटर।
जोहान गेर्ले

6
आपके प्रश्न का उत्तर देने के लिए मेरा उत्तर अपडेट किया गया। शुद्ध आभासी विध्वंसक एक प्राप्त करने का एक वैध तरीका है (प्राप्त करने का एकमात्र तरीका?) एक इंटरफ़ेस वर्ग जहां सभी विधियों में डिफ़ॉल्ट कार्यान्वयन हैं।
रोलीलैंड

7

यदि आप Microsoft के C ++ कंपाइलर का उपयोग कर रहे हैं, तो आप निम्न कार्य कर सकते हैं:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

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


4
मैं यह नहीं देखता कि आपने novtableमानक का इस्तेमाल क्यों कियाvirtual void Bar() = 0;
फ्लेक्सो

2
यह इसके अतिरिक्त है (मैंने अभी-अभी लापता पाया है = 0;जिसे मैंने जोड़ा है)। यदि आप इसे नहीं समझते हैं, तो प्रलेखन पढ़ें।
मार्क इंगग्राम

मैंने इसे बिना पढ़े = 0;और मान लिया कि यह बिल्कुल वैसा ही करने का एक गैर-मानक तरीका था।
फ्लेक्सो

4

वहाँ क्या लिखा है इसके लिए एक छोटा सा अतिरिक्त:

सबसे पहले, सुनिश्चित करें कि आपका विध्वंसक भी शुद्ध आभासी है

दूसरा, जब आप लागू करते हैं तो आप वस्तुतः (सामान्य रूप से) विरासत में प्राप्त करना चाहते हैं, बस अच्छे उपायों के लिए।


मुझे आभासी विरासत पसंद है क्योंकि वैचारिक रूप से इसका मतलब है कि विरासत में मिला वर्ग का केवल एक उदाहरण है। माना जाता है कि यहां के वर्ग को कोई स्थान की आवश्यकता नहीं है, इसलिए यह बहुत अधिक हो सकता है। मैंने थोड़ी देर के लिए C ++ में MI नहीं किया है, लेकिन गैर-कानूनी विरासत में उत्थान नहीं होगा?
उड़ी

क्यों, ओह क्यों, किसी को भी इस मामले में शुद्ध आभासी बनाना चाहते हैं? इससे क्या लाभ होगा? आप व्युत्पन्न वर्गों पर बस कुछ ऐसा करने के लिए मजबूर करेंगे, जिसमें उन्हें शामिल करने की कोई आवश्यकता नहीं है - एक डॉटर।
जोहान गेर्ले

2
यदि ऐसी स्थिति है कि इंटरफ़ेस के लिए एक पॉइंटर के माध्यम से कोई वस्तु नष्ट हो जाएगी, तो आपको यह सुनिश्चित करना चाहिए कि विध्वंसक आभासी है ...
Uri

शुद्ध वर्चुअल विध्वंसक के साथ कुछ भी गलत नहीं है। यह आवश्यक नहीं है, लेकिन इसके साथ कुछ भी गलत नहीं है। एक व्युत्पन्न वर्ग में एक विध्वंसक को लागू करना शायद ही उस वर्ग के कार्यान्वयनकर्ता पर एक बड़ा बोझ है। आप ऐसा क्यों करेंगे, इसके लिए नीचे मेरा जवाब देखें।
Rodyland

वर्चुअल इनहेरिटेंस के लिए +1, क्योंकि इंटरफेस के साथ यह अधिक संभावना है कि क्लास दो या दो से अधिक रास्तों से इंटरफ़ेस निकालेगी। मैं इंटरफेस थो में संरक्षित विध्वंसक का विकल्प चुनता हूं।
doc

4

आप NVI (नॉन वर्चुअल इंटरफ़ेस पैटर्न) के साथ कार्यान्वित अनुबंध कक्षाओं पर भी विचार कर सकते हैं। उदाहरण के लिए:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

अन्य पाठकों के लिए, जिम हाइसलोप और हर्ब सटर द्वारा यह डॉ। डॉब्स लेख "कन्वर्सेशन: वस्तुतः तुम्हारा" थोड़ा और अधिक विस्तृत करता है कि कोई एनवीआई का उपयोग क्यों करना चाहता है।
user2067021

और हर्ब सटर द्वारा यह लेख "आभासीता" भी ।
user2067021

1

मैं अभी भी C ++ डेवलपमेंट में नया हूं। मैंने विजुअल स्टूडियो (वीएस) से शुरुआत की।

फिर भी, कोई भी __interfaceवीएस (.NET) में उल्लेख नहीं किया गया है । मुझे बहुत यकीन नहीं है कि यह एक इंटरफ़ेस घोषित करने का एक अच्छा तरीका है। लेकिन यह एक अतिरिक्त प्रवर्तन ( दस्तावेजों में उल्लिखित ) प्रदान करता है। ऐसे कि आपको स्पष्ट रूप से निर्दिष्ट नहीं करना है virtual TYPE Method() = 0;, क्योंकि यह स्वचालित रूप से परिवर्तित हो जाएगा।

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

हालाँकि, मैं इसका उपयोग नहीं करता क्योंकि मुझे क्रॉस प्लेटफॉर्म संकलन संगतता के बारे में चिंता है, क्योंकि यह केवल .NET के तहत उपलब्ध है।

अगर किसी के पास इसके बारे में कुछ भी दिलचस्प है, तो कृपया साझा करें। :-)

धन्यवाद।


0

हालांकि यह सच है कि virtualइंटरफ़ेस को परिभाषित करने के लिए वास्तविक मानक है, चलो क्लासिक सी-जैसे पैटर्न के बारे में नहीं भूलना चाहिए, जो सी ++ में एक निर्माता के साथ आता है:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

इसका यह फायदा है कि आप अपनी कक्षा को फिर से बनाने के बिना ईवेंट रनटाइम को फिर से बाँध सकते हैं (चूंकि सी ++ में पॉलीमॉर्फिक प्रकारों को बदलने के लिए एक वाक्यविन्यास नहीं है, यह गिरगिट वर्गों के लिए एक वैकल्पिक उपाय है)।

सुझाव:

  • आप इसे बेस क्लास के रूप में प्राप्त कर सकते हैं (आभासी और गैर-आभासी दोनों की अनुमति है) और clickअपने वंशज निर्माता में भरें ।
  • आपके पास फ़ंक्शन पॉइंटर protectedसदस्य के रूप में हो सकता है और एक publicसंदर्भ और / या गेट्टर हो सकता है।
  • जैसा कि ऊपर उल्लेख किया गया है, यह आपको रनटाइम में कार्यान्वयन को स्विच करने की अनुमति देता है। इस प्रकार यह राज्य के प्रबंधन का भी एक तरीका है। ifआपके कोड में s बनाम राज्य परिवर्तनों की संख्या के आधार पर , यह es या s की तुलना में तेज़ हो सकता है (टर्नअराउंड 3-4 s के आसपास होने की उम्मीद है , लेकिन हमेशा पहले मापें।switch()ifif
  • यदि आप चुनते हैं std::function<>समारोह संकेत से अधिक है, तो आप कर सकते हैं के भीतर अपने सभी वस्तु डेटा का प्रबंधन करने में सक्षम हो IBase। इस बिंदु से, आप IBase(जैसे std::vector<IBase>काम करेंगे) के लिए मूल्य योजनाबद्ध हो सकते हैं । ध्यान दें कि यह आपके संकलक और एसटीएल कोड के आधार पर धीमा हो सकता है; std::function<>फ़ंक्शन पॉइंटर्स या यहां तक ​​कि वर्चुअल फ़ंक्शंस की तुलना में ओवरहेड होने की प्रवृत्ति का वर्तमान कार्यान्वयन भी (यह भविष्य में बदल सकता है)।

0

यहाँ abstract classc ++ मानक की परिभाषा है

n4687

13.4.2

एक अमूर्त वर्ग एक ऐसा वर्ग है जिसे केवल किसी अन्य वर्ग के आधार वर्ग के रूप में इस्तेमाल किया जा सकता है; अमूर्त वर्ग की कोई भी वस्तु नहीं बनाई जा सकती है, सिवाय इसके कि उससे प्राप्त वर्ग के उप-विषय के रूप में। यदि यह कम से कम एक शुद्ध आभासी फ़ंक्शन है तो एक वर्ग सार है।


-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

परिणाम: आयत क्षेत्र: 35 त्रिभुज क्षेत्र: 17

हमने देखा कि कैसे एक अमूर्त वर्ग ने getArea () के संदर्भ में एक इंटरफ़ेस को परिभाषित किया और दो अन्य वर्गों ने एक ही फ़ंक्शन को लागू किया, लेकिन आकार के लिए विशिष्ट क्षेत्र की गणना करने के लिए अलग-अलग एल्गोरिथ्म के साथ।


5
यह वह नहीं है जिसे एक इंटरफ़ेस माना जाता है! यह केवल एक सार आधार वर्ग है जिसमें एक विधि है जिसे ओवरराइड करने की आवश्यकता है! इंटरफेस आमतौर पर ऑब्जेक्ट्स होते हैं जिनमें केवल विधि परिभाषाएं होती हैं - एक "अनुबंध" अन्य वर्गों को पूरा करना होता है जब वे इंटरफ़ेस को लागू करते हैं।
गिटारफ्लो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.