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




inheritance interface (10)

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


आप एनवीआई (गैर वर्चुअल इंटरफेस पैटर्न) के साथ लागू अनुबंध वर्गों पर भी विचार कर सकते हैं। उदाहरण के लिए:

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.
};

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

उलझन में?


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

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

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

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

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

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


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

  1. अगर कोई व्यक्ति IDemo ऑब्जेक्ट को मिटाने का प्रयास करता है तो अपरिभाषित व्यवहार से बचने के लिए अपने इंटरफ़ेस में वर्चुअल डिस्ट्रक्टर घोषित करें या संरक्षित गैर वर्चुअल 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
            }
    }
    

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

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

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

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

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

धन्यवाद।


वहां क्या लिखा है इसके लिए थोड़ा सा जोड़ा:

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

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


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

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

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

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

सी ++ 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;
  // ...
};

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

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

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

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

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

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

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 इंटरफ़ेस का उपयोग कर TheirShape है ... क्या होता है यदि वे अपने ड्रॉ फ़ंक्शन my_draw भी कहते हैं?)।

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


सी ++ मानक में abstract class की परिभाषा यहां दी गई है

n4687

13.4.2

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


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

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
        }
};

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





pure-virtual