मैं एक इंटरफ़ेस का प्रतिनिधित्व करने वाले वर्ग को कैसे सेटअप करूं? क्या यह सिर्फ एक सार आधार वर्ग है?
मैं एक इंटरफ़ेस का प्रतिनिधित्व करने वाले वर्ग को कैसे सेटअप करूं? क्या यह सिर्फ एक सार आधार वर्ग है?
जवाबों:
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
}
};
आपको वर्चुअल डिस्ट्रक्टर के लिए एक निकाय शामिल करने की आवश्यकता नहीं है - यह पता चलता है कि कुछ कंपाइलरों को एक खाली डिस्ट्रॉक्टर को अनुकूलित करने में परेशानी होती है और आप डिफ़ॉल्ट का उपयोग करना बेहतर समझते हैं।
=0
एक शरीर के साथ शुद्ध आभासी ( ) विध्वंसक को परिभाषित करना है। यहाँ लाभ यह है कि संकलक, सैद्धांतिक रूप से, यह देख सकता है कि व्यवहार्य अब कोई वैध सदस्य नहीं है, और इसे पूरी तरह से छोड़ दें। एक शरीर के साथ एक आभासी विध्वंसक के साथ, कहा जाता है कि विध्वंसक कहा जा सकता है (वस्तुतः) this
सूचक के माध्यम से निर्माण के बीच में (जब निर्मित वस्तु अभी भी Parent
प्रकार की है), और इसलिए संकलक को एक वैध व्यवहार्यता प्रदान करनी है। इसलिए यदि आप this
निर्माण के दौरान स्पष्ट रूप से वर्चुअल डिस्ट्रक्टर्स को नहीं बुलाते हैं :) आप कोड आकार पर बचत कर सकते हैं।
override
संकलन-समय तर्क के लिए अनुमति देने के लिए कीवर्ड निर्दिष्ट कर सकते हैं और मूल्य प्रकार की जाँच वापस कर सकते हैं । उदाहरण के लिए, बाल की घोषणा मेंvirtual void OverrideMe() override;
शुद्ध आभासी तरीकों के साथ एक कक्षा बनाएं। एक और वर्ग बनाकर इंटरफ़ेस का उपयोग करें जो उन आभासी तरीकों को ओवरराइड करता है।
एक शुद्ध आभासी विधि एक वर्ग विधि है जिसे आभासी के रूप में परिभाषित किया गया है और 0 को सौंपा गया है।
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
override
सी ++ 11 के साथ
संपूर्ण कारण आपके पास C # / Java में अमूर्त आधार वर्गों के अलावा एक विशेष इंटरफ़ेस प्रकार-श्रेणी है क्योंकि C # / Java एकाधिक वंशानुक्रम का समर्थन नहीं करता है।
C ++ कई उत्तराधिकार का समर्थन करता है, और इसलिए एक विशेष प्रकार की आवश्यकता नहीं है। एक गैर-सार (शुद्ध आभासी) विधियों के साथ एक सार आधार वर्ग कार्यात्मक रूप से C # / Java इंटरफ़ेस के बराबर है।
Thread
उदाहरण हो। मल्टीपल इनहेरिटेंस खराब डिज़ाइन के साथ-साथ कंपोज़िशन भी हो सकती है। यह सब मामले पर निर्भर करता है।
सी ++ में प्रति सेगमेंट "इंटरफ़ेस" की कोई अवधारणा नहीं है। AFAIK, इंटरफेस को पहली बार जावा में कई वंशानुक्रम की कमी के आसपास काम करने के लिए पेश किया गया था। यह अवधारणा काफी उपयोगी है, और एक सार आधार वर्ग का उपयोग करके C ++ में समान प्रभाव प्राप्त किया जा सकता है।
सार आधार वर्ग एक ऐसा वर्ग है जिसमें कम से कम एक सदस्य फ़ंक्शन (जावा लिंगो में विधि) एक शुद्ध आभासी फ़ंक्शन है जो निम्नलिखित वाक्यविन्यास का उपयोग करके घोषित किया गया है:
class A
{
virtual void foo() = 0;
};
एक सार आधार वर्ग को तत्काल नहीं किया जा सकता है, अर्थात आप वर्ग ए की एक वस्तु की घोषणा नहीं कर सकते हैं। आप केवल ए से कक्षाएं प्राप्त कर सकते हैं, लेकिन किसी भी व्युत्पन्न वर्ग जो foo()
इच्छाशक्ति का कार्यान्वयन प्रदान नहीं करते हैं, वह भी सार होगा। अमूर्त होने से रोकने के लिए, एक व्युत्पन्न वर्ग को उन सभी शुद्ध आभासी कार्यों के लिए कार्यान्वयन प्रदान करना होगा जो उसे विरासत में मिले हैं।
ध्यान दें कि एक सार आधार वर्ग एक इंटरफ़ेस से अधिक हो सकता है, क्योंकि इसमें डेटा सदस्य और सदस्य फ़ंक्शन हो सकते हैं जो शुद्ध आभासी नहीं हैं। एक अंतरफलक के बराबर केवल शुद्ध आभासी कार्यों के साथ किसी भी डेटा के बिना एक सार आधार वर्ग होगा।
और, जैसा कि मार्क रैनसम ने बताया, एक सार आधार वर्ग को किसी भी आधार वर्ग की तरह, एक आभासी विध्वंसक प्रदान करना चाहिए।
जहां तक मैं परीक्षण कर सकता था, आभासी विध्वंसक जोड़ना बहुत महत्वपूर्ण है। मैं के साथ बनाई गई वस्तुओं का उपयोग कर रहा हूं 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()
कभी नहीं कहा जाता है।
मेरा जवाब मूल रूप से दूसरों के समान है लेकिन मुझे लगता है कि दो अन्य महत्वपूर्ण चीजें हैं:
अपने इंटरफ़ेस में एक आभासी विध्वंसक की घोषणा करें या यदि कोई व्यक्ति किसी प्रकार की वस्तु को हटाने की कोशिश करता है तो अपरिभाषित व्यवहार से बचने के लिए एक संरक्षित गैर-आभासी एक बनाएं IDemo
।
एकाधिक विरासत के साथ समस्याओं से बचने के लिए आभासी विरासत का उपयोग करें। (जब हम इंटरफेस का उपयोग करते हैं तो कई बार अधिक वंशानुक्रम होता है।)
और अन्य उत्तरों की तरह:
एक और वर्ग बनाकर इंटरफ़ेस का उपयोग करें जो उन आभासी तरीकों को ओवरराइड करता है।
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
}
}
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
?)।
अद्यतन: गैर-विरासत आधारित बहुरूपता के बारे में कुछ नए संदर्भ हैं:
Circle
वर्ग एक खराब डिजाइन है। आपको Adapter
ऐसे मामलों में पैटर्न का उपयोग करना चाहिए । क्षमा करें यदि यह थोड़ा कठोर लगेगा, लेकिन Qt
विरासत के बारे में निर्णय लेने से पहले कुछ वास्तविक जीवन पुस्तकालय का उपयोग करने का प्रयास करें । वंशानुक्रम जीवन को बहुत आसान बनाता है।
Adapter
पैटर्न का उपयोग करके सर्कल को ठीक करने का एक उदाहरण (शायद विचारधारा पर) दे सकते हैं ? मुझे इसके फायदे देखने में दिलचस्पी है।
Square
पहले से ही वहाँ नहीं है? पूर्वज्ञान? इसलिए यह वास्तविकता से अलग है। और वास्तव में यदि आप "MyShape" लाइब्रेरी पर भरोसा करना चुनते हैं तो आप शुरू से ही इसके इंटरफेस को अपना सकते हैं। आकृतियों के उदाहरणों में कई निरर्थक हैं (जिनमें से एक यह है कि आपके पास दो Circle
संरचनाएं हैं), लेकिन एडेप्टर कुछ इस तरह दिखाई देगा -> ideone.com/UogjWk
ऊपर सभी अच्छे जवाब। एक अतिरिक्त चीज जिसे आपको ध्यान में रखना चाहिए - आपके पास एक शुद्ध आभासी विध्वंसक भी हो सकता है। फर्क सिर्फ इतना है कि आपको इसे लागू करने की आवश्यकता है।
उलझन में?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
ऐसा करने का मुख्य कारण यह है कि यदि आप इंटरफ़ेस विधियाँ प्रदान करना चाहते हैं, जैसा कि मेरे पास है, लेकिन उन्हें वैकल्पिक बनाना।
क्लास बनाने के लिए एक इंटरफ़ेस क्लास के लिए शुद्ध वर्चुअल विधि की आवश्यकता होती है, लेकिन आपके सभी वर्चुअल तरीकों में डिफ़ॉल्ट कार्यान्वयन होता है, इसलिए शुद्ध वर्चुअल बनाने के लिए एकमात्र तरीका विध्वंसक होता है।
व्युत्पन्न वर्ग में एक विध्वंसक को फिर से लागू करना कोई बड़ी बात नहीं है - मैं हमेशा अपने व्युत्पन्न वर्गों में एक विध्वंसक, आभासी या नहीं, को फिर से लागू करता हूं।
यदि आप Microsoft के C ++ कंपाइलर का उपयोग कर रहे हैं, तो आप निम्न कार्य कर सकते हैं:
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
मुझे यह दृष्टिकोण पसंद है क्योंकि इससे बहुत छोटे इंटरफ़ेस कोड होते हैं और उत्पन्न कोड का आकार काफी छोटा हो सकता है। नोविटेबल का उपयोग उस वर्ग में वाइबेटर पॉइंटर के सभी संदर्भ को हटा देता है, इसलिए आप इसे कभी भी सीधे नहीं कर सकते। यहां दस्तावेज़ देखें - novtable ।
novtable
मानक का इस्तेमाल क्यों कियाvirtual void Bar() = 0;
= 0;
जिसे मैंने जोड़ा है)। यदि आप इसे नहीं समझते हैं, तो प्रलेखन पढ़ें।
= 0;
और मान लिया कि यह बिल्कुल वैसा ही करने का एक गैर-मानक तरीका था।
वहाँ क्या लिखा है इसके लिए एक छोटा सा अतिरिक्त:
सबसे पहले, सुनिश्चित करें कि आपका विध्वंसक भी शुद्ध आभासी है
दूसरा, जब आप लागू करते हैं तो आप वस्तुतः (सामान्य रूप से) विरासत में प्राप्त करना चाहते हैं, बस अच्छे उपायों के लिए।
आप 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.
};
मैं अभी भी C ++ डेवलपमेंट में नया हूं। मैंने विजुअल स्टूडियो (वीएस) से शुरुआत की।
फिर भी, कोई भी __interface
वीएस (.NET) में उल्लेख नहीं किया गया है । मुझे बहुत यकीन नहीं है कि यह एक इंटरफ़ेस घोषित करने का एक अच्छा तरीका है। लेकिन यह एक अतिरिक्त प्रवर्तन ( दस्तावेजों में उल्लिखित ) प्रदान करता है। ऐसे कि आपको स्पष्ट रूप से निर्दिष्ट नहीं करना है virtual TYPE Method() = 0;
, क्योंकि यह स्वचालित रूप से परिवर्तित हो जाएगा।
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
हालाँकि, मैं इसका उपयोग नहीं करता क्योंकि मुझे क्रॉस प्लेटफॉर्म संकलन संगतता के बारे में चिंता है, क्योंकि यह केवल .NET के तहत उपलब्ध है।
अगर किसी के पास इसके बारे में कुछ भी दिलचस्प है, तो कृपया साझा करें। :-)
धन्यवाद।
हालांकि यह सच है कि 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()
if
if
std::function<>
समारोह संकेत से अधिक है, तो आप कर सकते हैं के भीतर अपने सभी वस्तु डेटा का प्रबंधन करने में सक्षम हो IBase
। इस बिंदु से, आप IBase
(जैसे std::vector<IBase>
काम करेंगे) के लिए मूल्य योजनाबद्ध हो सकते हैं । ध्यान दें कि यह आपके संकलक और एसटीएल कोड के आधार पर धीमा हो सकता है; std::function<>
फ़ंक्शन पॉइंटर्स या यहां तक कि वर्चुअल फ़ंक्शंस की तुलना में ओवरहेड होने की प्रवृत्ति का वर्तमान कार्यान्वयन भी (यह भविष्य में बदल सकता है)।यहाँ abstract class
c ++ मानक की परिभाषा है
n4687
13.4.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 () के संदर्भ में एक इंटरफ़ेस को परिभाषित किया और दो अन्य वर्गों ने एक ही फ़ंक्शन को लागू किया, लेकिन आकार के लिए विशिष्ट क्षेत्र की गणना करने के लिए अलग-अलग एल्गोरिथ्म के साथ।