2015-09-16 8 views
15

अधिभावी पढ़ना this answer, मैं काफी हैरान करता है, तो यह वास्तव में काम करता है अपने आप के लिए बाहर की कोशिश करने का वर्णन किया गया था:अजीब वाक्य रचना जब आभासी कार्यों

#include <iostream> 

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

class B { 
public: 
    virtual void foo() = 0; 
}; 

class C : public A, public B { 
public: 
    virtual void foo(); 
}; 

void C::foo(){ 
    std::cout << "C" << std::endl; 
} 
void C::A::foo(){ 
    std::cout << "A" << std::endl; 
} 
void C::B::foo(){ 
    std::cout << "B" << std::endl; 
} 

int main() { 
    C c; 
    static_cast<A*>(&c)->foo(); 
    static_cast<B*>(&c)->foo(); 
    c.foo(); 
    return 0; 
} 

मैं सच में नहीं लगता था कि इसमें से एक आभासी विधि ओवरराइड करने के लिए संभव हो गया था दो अलग-अलग आधार वर्ग, जिनका नाम और हस्ताक्षर है। और, जैसा कि मैंने अपेक्षा की थी, प्रोग्राम प्रिंट के ऊपर:

C 
C 
C 

तो उत्तर गलत है - जैसा कि मैंने शुरुआत से महसूस किया था। आश्चर्यजनक हिस्सा यह है: मेरा जीसीसी इस वाक्यविन्यास को क्यों स्वीकार करता है: void C::A::foo(){? मुझे इसका मतलब क्या हो सकता है पर कुछ भी नहीं मिला। क्या यह एक जीसीसी बग/फीचर है? क्या यह कुछ अस्पष्ट मानक सी ++ वाक्यविन्यास है? या क्या मैं पूरी तरह से स्थिति की गलत व्याख्या कर रहा हूं?

संपादित करें:

ऐसा लगता है कि void C::A::foo(){} सिर्फ इस संदर्भ में A::foo के लिए एक परिभाषा है। पर क्यों? क्या यह जीसीसी बग है? या यह मानक द्वारा किसी भी तरह की अनुमति है? यदि हां, तो क्या यह इस तरह की चीज़, या किसी प्रकार के सामान्य खंड के लिए विशिष्ट नियम है (कहें: यदि कोई पहचानकर्ता समझ में नहीं आता है - जैसे 'सी :: ए :: फू', तो संकलक ऐसा करने के लिए स्वतंत्र है चाहता हे)।

+0

ध्यान दें कि यदि आप 'c.A :: foo()' लिखते हैं तो यह "ए" लिख देगा। –

+0

@ChrisDrew यह वाक्यविन्यास का मतलब है 'foo' के विशिष्ट संस्करण में एक स्थिर (गैर-आभासी) कॉल। मेरे संपादन के साथ, यह स्पष्ट है कि यह "ए" क्यों लिखता है। लेकिन यह किसी भी तरह से 'foo' के लिए वीएमटी प्रविष्टि नहीं बदलता है। –

+0

यह 'ए' का * इंजेक्शन-क्लास-नाम * है, जिसे 'सी' विरासत में मिला है। –

उत्तर

16

मुझे लगता है कि तथ्य यह है कि C::A::foo को परिभाषित करता है A::fooइंजेक्शन-वर्ग-नाम के कारण है।

N3337 [class]/2: एक वर्ग-नाम उस दायरे में डाला गया है जिसमें इसे कक्षा के नाम के तुरंत बाद घोषित किया जाता है। कक्षा का नाम कक्षा के दायरे में भी डाला गया है; इसे इंजेक्शन-क्लास-नाम के रूप में जाना जाता है। एक्सेस जांच के प्रयोजनों के लिए, इंजेक्शन-क्लास-नाम का इलाज इस तरह किया जाता है कि यह एक सार्वजनिक सदस्य नाम था। [...]

A के बाद से C का एक आधार वर्ग है और नाम AA के दायरे में इंजेक्ट किया जाता, A भी C से दिख रहा है।

को देखते हुए ऊपर, हम की तरह बुरी तरह विकृत कर सकते हैं:

void C::C::C::A::A::A::foo(){ 
    std::cout << "A" << std::endl; 
} 

Live Demo

+1

को स्पष्ट कर दूंगा ... इसलिए इस वाक्यविन्यास में आभासी कार्यों को ओवरराइड करने के साथ कुछ लेना देना नहीं है - जैसा कि मैंने सोचा था। धन्यवाद। –

7

दिलचस्प बात यह

void C::A::foo(){ 
    std::cout << "A" << std::endl; 
} 

को परिभाषित करता है A::foo() (यदि आप चाहते हैं आप एक शुद्ध आभासी समारोह के लिए एक कार्यान्वयन प्रदान कर सकते हैं)। आप यह जांच सकते हैं कि यह अतिरिक्त परिभाषा जोड़कर वास्तव में मामला है:

void A::foo(){ 
    std::cout << "Base A" << std::endl; 
} 
void C::A::foo(){ 
    std::cout << "A" << std::endl; 
} 

जीसीसी एक एकाधिक परिभाषा त्रुटि की रिपोर्ट करेगा। यह शायद समझ में आता है, क्योंकि आप C::A को A के नाम उपनाम के रूप में देख सकते हैं।

क्या मानक कहते हैं, मैं वर्तमान में जब इस standardese के समुद्र में उचित है की सटीक परिभाषा खोजने के लिए संघर्ष कर रहा हूँ, लेकिन मैं Paragraph 3.4.3.1.2 में एक संबंधित उदाहरण देख सकते हैं के संदर्भ में:

struct A { A(); }; 
struct B: public A { B(); }; 
A::A() { } 
B::B() { } 
B::A ba; // object of type A // <-- here 
A::A a; // error, A::A is not a type name 
struct A::A a2; // object of type A 

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

+0

"यह शायद समझ में आता है, क्योंकि आप 'ए' के ​​लिए नाम उपनाम के रूप में' C :: A' देख सकते हैं - क्या आप वास्तव में कर सकते हैं? क्या यह किसी भी तरह मानक द्वारा अनिवार्य है? –

+1

मैंने पहले से ही एक और जवाब स्वीकार कर लिया है, क्योंकि यह बेहतर मानक उद्धरण प्रदान करता है। यह कन्स्ट्रक्टर रिज़ॉल्यूशन के साथ बहुत कम नहीं है। ऐसा इसलिए है क्योंकि 'ए' को 'ए' गुंजाइश में इंजेक्शन दिया गया है, और इस प्रकार 'सी' गुंजाइश में भी। –

+0

हाँ, मैंने इसे देखा है। मेरा मतलब था कि उदाहरण कन्स्ट्रक्टर रिज़ॉल्यूशन के साथ करना था और मुझे नहीं पता कि उदाहरण में लागू वास्तविक नियम कौन सा है। मैं इस –

2

दृश्य स्टूडियो संकलक (2008) की अनुमति नहीं है

void C::A::foo(){ 
    std::cout << "A" << std::endl; 
} 
void C::B::foo(){ 
    std::cout << "B" << std::endl; 

लेकिन केवल

void A::foo(){ 
    std::cout << "A" << std::endl; 
} 
void B::foo(){ 
    std::cout << "B" << std::endl; 

क्योंकि यह शुद्ध वर्चुअल विधियों के लिए परिभाषा प्रदान करने के लिए पूरी तरह से मान्य है। व्युत्पन्न वर्गों है कि इस शुद्ध आभासी समारोह उनके कोड में इस कार्यान्वयन smwhere कॉल कर सकते हैं लागू:

"प्रभावी सी ++" मेयर्स एक शुद्ध आभासी समारोह के लिए एक कारण एक शरीर का उल्लेख है। यदि दो अलग व्युत्पन्न कक्षाओं के कोड के भाग समान हैं तो यह पदानुक्रम में इसे स्थानांतरित करने के लिए समझ बनाता है, भले ही फ़ंक्शन शुद्ध आभासी होना चाहिए।

here (forum.codeguru.com) देखें।

एक शुद्ध आभासी समारोह गतिशील रूप से नहीं कहा जा सकता, लेकिन स्थिर:

C c; 
static_cast<A*>(&c)->foo(); // prints C 
static_cast<B*>(&c)->foo(); // prints C, dynamic dispatch 
c.foo();     // prints C, dynamic dispatch 
c.A::foo();     // prints A, static dispatch 
c.B::foo();     // prints B, static dispatch 

स्पष्टीकरण:

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

void Derived::f() 
{ Base::f(); } 

देखें here (isocpp.org)

+0

एक और उत्तर से ऐसा लगता है कि 'शून्य सी :: ए :: foo() {}' की अनुमति दी जानी चाहिए। इंजेक्शन-क्लास-नाम कुछ नया (सी ++ 11) अतिरिक्त है? यदि ऐसा है तो मैं इसे MSVC2008 में देखने की उम्मीद नहीं करूंगा - लेकिन यदि यह सुविधा पुरानी है, तो यह एमएसवीसी बग है। –

+0

कम से कम इंजेक्शन-क्लास-नाम सी ++ 03 में भी है, [वर्ग]/2 पर भी। – TartanLlama

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