2010-06-16 17 views
5

(यह सवाल शायद Stroustrup के लिए एक संदर्भ के साथ उत्तर दिया जाना चाहिए।)सी ++ आपको सबसे ज्यादा व्युत्पन्न कक्षा में पॉइंटर का अनुरोध करने की अनुमति क्यों नहीं देता है?

यह के रूप में, सबसे व्युत्पन्न वर्ग के सूचक के अनुरोध करने के लिए निम्नलिखित सक्षम होने के लिए अत्यंत उपयोगी लगता है:

class Base { ... }; 
class DerivedA { ... }; 
class DerivedB { ... }; 
class Processor 
{ 
    public: 
    void Do(Base* b) {...} 
    void Do(DerivedA* d) {...} 
    void Do(DerivedB* d) {...} 
}; 

list<Base*> things; 
Processor p; 
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i) 
{ 
    p.Do(CAST_TO_MOST_DERIVED_CLASS(*i)); 
} 

लेकिन यह तंत्र सी ++ में प्रदान नहीं किया गया है। क्यूं कर?

अद्यतन, प्रेरित उदाहरण: के बजाय बेस होने के

मान लीजिए और व्युत्पन्न और प्रोसेसर, तुम हो:

class Fruit 
class Apple : public Fruit 
class Orange: public Fruit 

class Eater 
{ 
    void Eat(Fruit* f) { ... } 
    void Eat(Apple* f) { Wash(f); ... } 
    void Eat(Orange* f) { Peel(f); ... } 
}; 

Eater me; 
for each Fruit* f in Fruits 
    me.Eat(f); 

लेकिन यह सी में करने के लिए ++ मुश्किल है, आगंतुक की तरह रचनात्मक समाधान की आवश्यकता होती है पैटर्न। सवाल यह है कि: सी ++ में ऐसा करना मुश्किल क्यों है, जब "CAST_TO_MOST_DERIVED" जैसी कुछ चीज़ इसे अधिक सरल बनाती है?

अपडेट: विकिपीडिया सभी

मुझे लगता है कि पुन्तुस Gagge एक अच्छा जवाब है जानता है। यह करने के लिए Multiple Dispatch पर विकिपीडिया प्रविष्टि से इस बिट जोड़ें:

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

पृष्ठभूमि के लिए, आप Multi-Methods के बारे में थोड़ा सारांश है, जो एक मैं उल्लेख की तरह एक फोन से बेहतर होगा, क्योंकि वे सिर्फ काम करेंगे पढ़ सकते हैं।

+4

मैंने पहले कुछ क्रैक डिज़ाइन देखा है, लेकिन ... ओह मैन ... 'बेस :: डू()', 'DerivedA :: Do() ',' DerivedB :: Do() 'और समस्या दूर जाता है। –

+0

लेकिन कभी-कभी यह कोई विकल्प नहीं है, या यह अर्थात् सबसे अच्छा विकल्प नहीं है। नीचे डेडएमजी को प्रतिक्रिया देखें। –

उत्तर

4

क्या आप क्रम प्रकार पर एक switch के बराबर होगा सुझाव दे रहे हैं, अतिभारित कार्यों में से एक करार दिया। के रूप में दूसरों का संकेत दिया है, तो आप अपने वंशानुगत पदानुक्रम के साथ काम करना चाहिए, और नहीं के खिलाफ यह: अपने वर्ग पदानुक्रम के बजाय इसे बाहर भेजने में virtuals का उपयोग करें।

उसने कहा, ऐसा कुछ double dispatch के लिए उपयोगी हो सकता है, खासकर यदि आपके पास Processors का पदानुक्रम भी है। लेकिन संकलक इसे कैसे कार्यान्वित करेगा?

सबसे पहले, आप को निकालने के लिए क्या आप क्रम में 'सबसे अतिभारित प्रकार' फोन होगा। यह किया जा सकता है, लेकिन आप कैसे निपटेंगे उदा। एकाधिक विरासत और टेम्पलेट्स? किसी भाषा में प्रत्येक सुविधा को अन्य सुविधाओं के साथ अच्छी तरह से बातचीत करनी चाहिए - और सी ++ में बड़ी संख्या में विशेषताएं हैं!

दूसरा, अपने कोड उदाहरण काम करने के लिए, आप क्रम प्रकार के आधार पर सही स्थिर अधिभार भी प्राप्त करना होगा (जो सी ++ की अनुमति नहीं है के रूप में यह डिज़ाइन किया गया है)। क्या आप इसे compile time lookup rules का पालन करना चाहते हैं, खासकर एकाधिक पैरामीटर के साथ? क्या आप इस रनटाइम प्रेषण को अपने Processor पदानुक्रम के रनटाइम प्रकार पर विचार करने के लिए पसंद करेंगे, और उन्होंने क्या अधिभार जोड़ा है? आप अपने रनटाइम प्रेषक में स्वचालित रूप से जोड़ने के लिए संकलक को कितना तर्क पसंद करेंगे? आप अमान्य रनटाइम प्रकारों से कैसे निपटेंगे? क्या फीचर के उपयोगकर्ता साधारण कलाकार और फ़ंक्शन कॉल की तरह दिखने की लागत और जटिलता से अवगत होंगे?

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

+1

आपको डबल-प्रेषण के लिए इसकी आवश्यकता नहीं है। आप प्रत्येक के लिए प्रोसेसर का अधिभार प्रदान करते हैं, जैसा कि उसने किया है, फिर आप बेस में वर्चुअल कॉल को कॉल करते हैं, जो प्रोसेसर को पास करेगा, इस प्रकार सही ओवरलोड को कॉल करेगा, जिसे प्रोसेसर विरासत को प्रेषित किया जा सकता है। – Puppy

+0

@DeadMG ध्यान दें कि वह आपको डबल प्रेषण के लिए/आवश्यकता/नहीं कहता है, उसने कहा कि यह डबल प्रेषण (और उस मामले के लिए एकाधिक प्रेषण) के लिए उपयोगी/उपयोगी होगा। –

+0

@ पोंटस आह! वास्तविक सवाल का जवाब! मुझे स्वीकार करने से पहले मुझे थोड़ा और पढ़ने दो ... –

4

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

कोई तंत्र प्रदान नहीं किया गया है क्योंकि यह पूरी तरह से अनावश्यक और अनावश्यक है।

मैं कसम खाता हूं, मैंने एक या दो दिन पहले इस सटीक प्रश्न को मैदान में रखा था।

+2

नहीं, यह अनावश्यक और अनावश्यक नहीं है। यह कई प्रकार की परिस्थितियों में उपयोगी होगा जो एकाधिक प्रेषण की आवश्यकता है। (इसलिए, उदाहरण के लिए, मान लीजिए कि आप DerivedA/B को संशोधित नहीं कर सकते हैं या यह अर्थात्, आधार/व्युत्पन्न में Do() डालने का अर्थ नहीं है। यदि समस्या नहीं है तो मैं एक और ठोस उदाहरण प्रदान करूंगा। टी स्पष्ट करें।) –

+2

कोई भी कार्य जहां कक्षा को आधार नहीं है, तो व्यवहार को बदलने की आवश्यकता है, वर्चुअल पदानुक्रम में जाता है। यदि आपके अर्थशास्त्र इसके लिए अलग हैं, तो यह आपके अर्थशास्त्र को ठीक करने का समय है। यदि आप व्युत्पन्न को परिवर्तित नहीं कर सकते हैं, तो शिकायत करें कि जो भी कर सकता है, क्योंकि उन्होंने आपको खराब कर दिया है। – Puppy

+0

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

8

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

+0

दरअसल, वर्चुअल फ़ंक्शंस आपके लिए ऐसा नहीं करते हैं, क्योंकि वर्चुअल फ़ंक्शन बेस/व्युत्पन्न क्लास में होना चाहिए, जो आधार/व्युत्पन्न कक्षा में फ़ंक्शन/नहीं/जैसा नहीं है। मैं एक प्रेरक उदाहरण के साथ संपादित करेंगे। –

+0

@ मैथ्यू लोवे: आपका प्रश्न था "लेकिन यह तंत्र सी ++ में प्रदान नहीं किया गया है। क्यों?" जहां "यह" सबसे व्युत्पन्न प्रकार के लिए एक कलाकार की कमी थी। वर्चुअल फ़ंक्शंस आपको विज़िटर पैटर्न जैसी चीज़ों के माध्यम से प्राप्त करने की अनुमति देता है। जाहिर है कि आपको चीजों को अलग-अलग तरीके से डिजाइन करना है जिस तरह से आप उम्मीद कर रहे थे लेकिन वे आपके लिए समस्या का समाधान कर सकते हैं।बीटीडब्लू, मैंने आपके द्वारा पूछे गए प्रश्न का उत्तर दिया, @ पोनट्स गैग के उत्तर पर आपकी टिप्पणी में निहितार्थ के विपरीत। – Troubadour

6

क्योंकि मेरा प्रकार संकलन समय पर निर्धारित नहीं है। इसलिए संकलक नहीं जानता कि कौन सा फ़ंक्शन कॉल उत्पन्न होता है। सी ++ केवल गतिशील प्रेषण की एक विधि का समर्थन करता है जो वर्चुअल फ़ंक्शन तंत्र है।

+0

एयू contraire! मैं गतिशील_कास्ट में कई कॉल का उपयोग कर सकता हूं और वास्तव में काम पूरा कर सकता हूं। सवाल यह है, "मैं इसके बजाय सबसे व्युत्पन्न कक्षा क्यों नहीं मांग सकता?" –

+1

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

+0

मुझे नहीं लगता कि मैंने सही तरीके से अपना इरादा बताया है। "अधिकांश व्युत्पन्न वर्ग में डाला गया" का मेरा मतलब है, यदि वस्तु वास्तव में एक व्युत्पन्न बी है, तो मुझे एक व्युत्पन्न बी * वापस करने के लिए एक CAST_TO_MOST_DERIVED_CLASS (& ऑब्जेक्ट) चाहिए। यदि यह एक व्युत्पन्न सी है, तो मुझे एक व्युत्पन्न सी * चाहिए। –

0

सी ++ संबंधित प्रकार के संदर्भ में डेटा का अर्थ देता है। जब आप किसी सूची में DerivedA * या DerivedB * का एक उदाहरण संग्रहीत करते हैं, तो उस सहयोगी प्रकार को आवश्यक रूप से बेस * होना चाहिए। इसका मतलब यह है कि संकलक स्वयं यह निर्धारित नहीं कर सकता कि वे बेस क्लास के बजाय उप-वर्गों में से एक के लिए पॉइंटर्स हैं।सिद्धांत रूप में आप संबंधित प्रकार की विरासत को देखकर एक कम व्युत्पन्न वर्ग में जा सकते हैं, जो आप चाहते हैं उसे करने के लिए आवश्यक जानकारी संकलन-समय पर उपलब्ध नहीं है।

8

सबसे पहले, सी ++ आपको संख्यात्मक शर्तों (यानी केवल पते का संख्यात्मक मान) में सबसे अधिक व्युत्पन्न कक्षा में सूचक के लिए अनुरोध करने की अनुमति देता है। यह dynamic_cast से void* करता है।

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

,, पूरी तरह से अलग तरह से कार्यान्वित किया जाता है क्या आप को लागू करने (अगर मैं अपने इरादे को सही ढंग से समझ में आ) की कोशिश कर रहे एक कलाकारों द्वारा नहीं। एक उदाहरण के लिए डबल प्रेषण पढ़ें।

+0

'void *' के साथ 'dynamic_cast'? वास्तव में? मैं इस धारणा के तहत था कि आप ऐसा नहीं कर सके। बेशक, यह संभव होगा * के बाद * आपने पॉइंटर प्रकार को और भी बदल दिया, और फिर गतिशील कास्ट काम करेगा .. – ianmac45

+1

@ ianmac45: नहीं। परिभाषा के अनुसार, 'dynamic_cast (पी)' (जहां 'p' है एक पॉलीमोर्फिक प्रकार के लिए एक सूचक) * सबसे व्युत्पन्न वस्तु * के लिए एक सूचक को मूल्यांकन करता है। बेशक, यह केवल इतना संख्यात्मक है। परिणाम प्रकार अभी भी 'शून्य * 'है। – AnT

+0

ध्यान दें कि मेरा प्रश्न यह नहीं है कि "क्या आप इसे सी ++ में कर सकते हैं" लेकिन "आप इसे C++ में क्यों नहीं कर सकते"। मुझे लगता है कि पोंटस गैग इस सवाल का जवाब देने का एक अच्छा काम करता है। –

2

सी ++ ओवरलोड रिज़ॉल्यूशन संकलन समय पर होता है। आपके उदाहरण को रनटाइम पर *i के वास्तविक प्रकार का निर्धारण करने की आवश्यकता होगी। रनटाइम पर इसे करने के लिए रनटाइम प्रकार की जांच की आवश्यकता होगी, और क्योंकि सी ++ एक प्रदर्शन उन्मुख भाषा है, यह उद्देश्य से इस लागत से बचाता है। यदि आप वास्तव में ऐसा करना चाहते थे (और मैं एक और यथार्थवादी उदाहरण देखने के लिए उत्सुक हूं) तो आप सबसे व्युत्पन्न वर्ग में गतिशील_कास्ट कर सकते हैं, फिर यदि वह दूसरी सबसे व्युत्पन्न कक्षा में विफल रहता है, और इसी तरह, लेकिन इसके बारे में जानने की आवश्यकता है सामने पदानुक्रम ऊपर। और पूर्ण पदानुक्रम को आगे बढ़ना असंभव हो सकता है - यदि डेरिवेटबी कक्षा सार्वजनिक हेडर में है, तो संभव है कि एक और लाइब्रेरी इसका उपयोग करे और एक और अधिक व्युत्पन्न वर्ग बनाये।

2

आप double dispatch देख रहे हैं। यह सी ++ में किया जा सकता है, जैसा कि उस लिंक पर दिखाया गया है, लेकिन यह सुंदर नहीं है, और इसमें मूल रूप से एक दूसरे को कॉल करने वाले दो आभासी कार्यों का उपयोग करना शामिल है। यदि आप अपने विरासत के पेड़ में कुछ वस्तुओं को संशोधित नहीं कर सकते हैं, तो आप इस तकनीक का उपयोग करने में सक्षम नहीं हो सकते हैं।

+0

असल में, मैं डबल प्रेषण की तलाश नहीं कर रहा हूं, क्योंकि मुझे पहले से ही पता है। इसके बजाए, मैं जानना चाहता हूं कि यह भाषा सुविधा क्यों उपलब्ध नहीं है। वास्तविक प्रश्न का उत्तर देने के लिए –

1

यह सी ++ में संभव नहीं है, लेकिन क्या हासिल करना चाहते हैं का उपयोग कर आसानी से संभव है Visitor design pattern:

class Base 
{ 
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); } 
}; 

class DerivedA 
{ 
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); } 
}; 

class DerivedB 
{ 
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); } 
}; 

class BaseVisitor 
{ 
    virtual void visit(Base* b) = 0; 
    virtual void visit(DerivedA* d) = 0; 
    virtual void visit(DerivedB* d) = 0; 
}; 

class Processor : public BaseVisitor 
{ 
    virtual void visit(Base* b) { ... } 
    virtual void visit(DerivedA* d) { ... } 
    virtual void visit(DerivedB* d) { ... } 
}; 

list<Base*> things; 
Processor p; 
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i) 
{ 
    (*i)->visit(p); 
} 
1

करता क्यों नहीं सी ++ यह है? शायद रचनाकारों ने कभी इसके बारे में सोचा नहीं। या शायद वे इसे उपयुक्त या उपयोगी नहीं मानते थे। या शायद इस भाषा में इसे करने की कोशिश करने में समस्याएं थीं।

चलें कहना इस सुविधा मौजूद है ताकि संकलक कोड गतिशील प्रकार की ओर इशारा किया की जांच करता है और उचित अधिभार कॉल लिखेंगे:

कि पिछले संभावना पर, यहाँ एक सोचा प्रयोग है। अब यह भी कहें कि कोड का एक अलग भाग class DerivedC : Base {...}; है। और कहें कि संबंधित Processor::Do अधिभार जोड़ा गया है।

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

हां, ऐसी कार्यक्षमता लिखना स्वयं एक ही समस्या के लिए अतिसंवेदनशील होगा, लेकिन वहां प्रोग्रामर का व्यवहार चुनने के लिए कुल नियंत्रण होता है, न कि संकलक।

+0

बोनस अंक। =) –

संबंधित मुद्दे

 संबंधित मुद्दे