नोट: निम्नलिखित C ++ 03 कोड है, लेकिन हमें अगले दो वर्षों में C ++ 11 के लिए एक कदम की उम्मीद है, इसलिए हमें इसे ध्यान में रखना चाहिए।
C ++ में अमूर्त इंटरफ़ेस लिखने के तरीके के बारे में (अन्य लोगों के बीच) के लिए एक दिशानिर्देश लिख रहा हूं। मैंने इस विषय पर सटर के दोनों लेख पढ़े हैं, उदाहरणों और उत्तरों के लिए इंटरनेट पर खोज की, और कुछ परीक्षण किए।
इस कोड को संकलित नहीं करना चाहिए!
void foo(SomeInterface & a, SomeInterface & b)
{
SomeInterface c ; // must not be default-constructible
SomeInterface d(a); // must not be copy-constructible
a = b ; // must not be assignable
}
उपरोक्त सभी व्यवहार स्लाइसिंग में उनकी समस्या के स्रोत का पता लगाते हैं : सार इंटरफ़ेस (या पदानुक्रम में गैर-पत्ती वर्ग) रचनात्मक नहीं होना चाहिए और न ही मैथुन योग्य / असाइन किया जाना चाहिए, यदि व्युत्पन्न वर्ग हो सकता है।
0 समाधान: बुनियादी इंटरफ़ेस
class VirtuallyDestructible
{
public :
virtual ~VirtuallyDestructible() {}
} ;
यह समाधान सादा है, और कुछ हद तक भोला है: यह हमारे सभी अवरोधों को विफल करता है: यह डिफ़ॉल्ट रूप से निर्मित, कॉपी-निर्माण और कॉपी-असाइन किया जा सकता है (मैं कदम बिल्डरों और असाइनमेंट के बारे में निश्चित नहीं हूं, लेकिन मुझे अभी भी 2 साल का समय है यह बाहर)।
- हम विध्वंसक को शुद्ध आभासी घोषित नहीं कर सकते क्योंकि हमें इसे इनलाइन रखने की आवश्यकता है, और हमारे कुछ संकलक इनलाइन खाली शरीर के साथ शुद्ध आभासी तरीकों को पचा नहीं पाएंगे।
- हां, इस वर्ग का एकमात्र उद्देश्य कार्यान्वयनकर्ताओं को लगभग विनाशकारी बनाना है, जो एक दुर्लभ मामला है।
- यहां तक कि अगर हमारे पास एक अतिरिक्त आभासी शुद्ध विधि है (जो कि अधिकांश मामलों में है), तो यह वर्ग अभी भी कॉपी-असाइन किया जाएगा।
तो, नहीं ...
पहला समाधान: बढ़ावा :: नॉनकॉपीबल
class VirtuallyDestructible : boost::noncopyable
{
public :
virtual ~VirtuallyDestructible() {}
} ;
यह समाधान सबसे अच्छा है, क्योंकि यह स्पष्ट है, स्पष्ट है, और C ++ (कोई मैक्रो)
समस्या यह है कि यह अभी भी उस विशिष्ट इंटरफ़ेस के लिए काम नहीं करता है क्योंकि वस्तुतःConstructible अभी भी डिफ़ॉल्ट रूप से निर्मित किया जा सकता है ।
- हम विध्वंसक को शुद्ध आभासी घोषित नहीं कर सकते क्योंकि हमें इसे इनलाइन रखने की आवश्यकता है, और हमारे कुछ संकलक इसे पचा नहीं पाएंगे।
- हां, इस वर्ग का एकमात्र उद्देश्य कार्यान्वयनकर्ताओं को लगभग विनाशकारी बनाना है, जो एक दुर्लभ मामला है।
एक और समस्या यह है कि गैर-प्रतिलिपि योग्य इंटरफ़ेस को लागू करने वाली कक्षाओं को तब कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर को स्पष्ट रूप से घोषित / परिभाषित करना होगा यदि उन्हें उन तरीकों की आवश्यकता है (और हमारे कोड में, हमारे पास मूल्य वर्ग हैं जो अभी भी हमारे क्लाइंट द्वारा एक्सेस किए जा सकते हैं इंटरफेस)।
यह शून्य के नियम के खिलाफ जाता है, जहां हम जाना चाहते हैं: यदि डिफ़ॉल्ट कार्यान्वयन ठीक है, तो हमें इसका उपयोग करने में सक्षम होना चाहिए।
दूसरा समाधान: उन्हें संरक्षित करें!
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
// With C++11, these methods would be "= default"
MyInterface() {}
MyInterface(const MyInterface & ) {}
MyInterface & operator = (const MyInterface & ) { return *this ; }
} ;
यह पैटर्न हमारे पास मौजूद तकनीकी बाधाओं (कम से कम उपयोगकर्ता कोड में) का अनुसरण करता है : MyInterface डिफ़ॉल्ट रूप से निर्मित नहीं किया जा सकता है, कॉपी-निर्माण नहीं किया जा सकता है और कॉपी-असाइन नहीं किया जा सकता है।
इसके अलावा, यह कक्षाओं को लागू करने में कोई कृत्रिम बाधा नहीं डालता है , जो तब शून्य के नियम का पालन करने के लिए स्वतंत्र हैं, या यहां तक कि कुछ बिल्डरों / ऑपरेटरों को बिना समस्या के C ++ 11/14 में "= डिफ़ॉल्ट" घोषित करते हैं।
अब, यह काफी क्रियात्मक है, और एक विकल्प एक मैक्रो का उपयोग करना होगा, कुछ इस तरह से:
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;
संरक्षित मैक्रो के बाहर रहना चाहिए (क्योंकि इसमें कोई गुंजाइश नहीं है)।
सही ढंग से "नामस्थान" (जो आपकी कंपनी या उत्पाद के नाम के साथ उपसर्ग है), मैक्रो हानिरहित होना चाहिए।
और लाभ यह है कि कोड को सभी स्रोतों में कॉपी किए जाने के बजाय एक स्रोत में विभाजित किया गया है। क्या चाल-निर्माण और चाल-असाइनमेंट को भविष्य में उसी तरह स्पष्ट रूप से अक्षम किया जाना चाहिए, यह कोड में बहुत हल्का परिवर्तन होगा।
निष्कर्ष
- क्या मैं चाहता हूं कि इंटरफेस में स्लाइसिंग के खिलाफ कोड को संरक्षित किया जाए? (मेरा मानना है कि मैं नहीं हूँ, लेकिन एक कभी नहीं जानता ...)
- उपरोक्त लोगों में सबसे अच्छा समाधान क्या है?
- क्या कोई दूसरा, बेहतर उपाय है?
कृपया याद रखें कि यह एक ऐसा पैटर्न है जो न्यूबरी (दूसरों के बीच) के लिए एक दिशानिर्देश के रूप में काम करेगा, इसलिए एक समाधान जैसे: "प्रत्येक मामले में इसका कार्यान्वयन होना चाहिए" एक व्यवहार्य समाधान नहीं है।
बाउंटी और परिणाम
प्रश्नों के उत्तर देने के लिए समय व्यतीत करने और उत्तरों की प्रासंगिकता के कारण मैंने इनाम को कोरडंप को प्रदान किया ।
समस्या का मेरा समाधान शायद कुछ इस तरह से होगा:
class MyInterface
{
DECLARE_CLASS_AS_INTERFACE(MyInterface) ;
public :
// the virtual methods
} ;
... निम्नलिखित मैक्रो के साथ:
#define DECLARE_CLASS_AS_INTERFACE(ClassName) \
public : \
virtual ~ClassName() {} \
protected : \
ClassName() {} \
ClassName(const ClassName & ) {} \
ClassName & operator = (const ClassName & ) { return *this ; } \
private :
यह निम्नलिखित कारणों से मेरी समस्या का एक व्यवहार्य समाधान है:
- इस वर्ग को तत्काल नहीं बनाया जा सकता है (निर्माणकर्ता संरक्षित हैं)
- यह वर्ग वस्तुतः नष्ट हो सकता है
- इस वर्ग को विरासत की कक्षाओं पर अनुचित बाधाएं लादे बिना विरासत में लिया जा सकता है (जैसे विरासत वर्ग डिफ़ॉल्ट मैथुन योग्य हो सकता है)
- मैक्रो के उपयोग का अर्थ है कि इंटरफ़ेस "घोषणा" आसानी से पहचाने जाने योग्य (और खोज योग्य) है, और इसके कोड को एक जगह पर फैक्टर किया जाता है जिससे इसे संशोधित करना आसान हो जाता है (एक उपयुक्त उपसर्ग नाम अवांछनीय नाम झड़पों को हटा देगा)
ध्यान दें कि अन्य उत्तरों ने बहुमूल्य अंतर्दृष्टि दी। आप सभी को धन्यवाद जिन्होंने इसे शॉट दिया।
ध्यान दें कि मुझे लगता है कि मैं अभी भी इस सवाल पर एक और इनाम रख सकता हूं, और मैं पर्याप्त जवाब देता हूं जो मुझे देखना चाहिए, मैं उस जवाब को देने के लिए एक इनाम खोलूंगा।
virtual ~VirtuallyDestructible() = 0
इंटरफ़ेस कक्षाओं (सार सदस्यों के साथ) की आभासी विरासत चाहिए। आप संभावना है कि वस्तुतःDestructible छोड़ सकते हैं।
virtual void bar() = 0;
उदाहरण के लिए? जो आपके इंटरफ़ेस को इंस्टेंश होने से रोकेगा।