2013-05-23 6 views
6

से वर्चुअल फ़ंक्शन को कॉल करना मैं Effective C++ पढ़ रहा हूं, और "आइटम 9: कभी भी निर्माण या विनाश के दौरान वर्चुअल फ़ंक्शंस को कॉल न करें"। और मैं सोच रहा हूं कि मेरा कोड ठीक है, भले ही यह इस नियम को तोड़ता है:कन्स्ट्रक्टर

using namespace std; 

class A{ 
    public: 
     A(bool doLog){ 
      if(doLog) 
       log(); 
     } 

     virtual void log(){ 
      cout << "logging A\n"; 
     } 
}; 


class B: public A{ 
public: 
    B(bool doLog) : A(false){ 
     if(doLog) 
      log(); 
    } 

    virtual void log(){ 
     cout << "logging B\n"; 
    } 
}; 


int main() { 
    A a(true); 
    B b(true); 
} 

क्या इस दृष्टिकोण में कुछ गड़बड़ है? जब मैं कुछ और जटिल करता हूं तो क्या मुझे परेशानी हो सकती है?

यह मुझे बताता है कि अधिकांश उत्तरों ने जो किया वह मुझे नहीं मिला, और उन्होंने बस फिर से समझाया कि कन्स्ट्रक्टर से वर्चुअल फ़ंक्शन को खतरनाक क्यों कहा जा रहा है।

मैं तनाव करने के लिए अपने कार्यक्रम के उत्पादन में इस तरह दिखता है कि चाहते हैं:

logging A 
logging B 

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

+1

इस मामले में आपने इसे ठीक से दबाया है, लेकिन यदि अन्य लोगों को उस कोड गलतियों का उपयोग करने की आवश्यकता है तो होने वाली हैं। –

+2

@ जोचिमपिलबोर्ग यह सच नहीं है: व्यवहार * परिभाषित किया गया है। निर्माण के दौरान, वर्चुअल फ़ंक्शन कॉल अक्षम हैं (उदाहरण के लिए वर्तमान में निर्माण प्रकार का कार्यान्वयन किया जाता है)। –

उत्तर

11

और अगर मेरा कोड ठीक है, भले ही वह इस नियम को तोड़ता मैं सोच रहा हूँ:

यह आप "ठीक" से क्या मतलब है पर निर्भर करता है। आपका कार्यक्रम अच्छी तरह से गठित है, और इसका व्यवहार अच्छी तरह से परिभाषित है, इसलिए यह अनिर्धारित व्यवहार और उस तरह की सामग्री का आह्वान नहीं करेगा।

हालांकि, एक, अपेक्षा कर सकते हैं जब एक आभासी फ़ंक्शन की कॉल देखकर, कि कॉल सबसे व्युत्पन्न प्रकार जो कि समारोह को ओवरराइड करता है द्वारा प्रदान की कार्यान्वयन लागू द्वारा हल हो गई है।

कि निर्माण के दौरान छोड़कर, इसी subobject, अभी तक निर्माण नहीं किया गया है, इसलिए अधिकांश व्युत्पन्न subobject एक वर्तमान में निर्माण किया जा रहा है। परिणाम: कॉल प्रेषित किया गया है जैसे कि कार्य वर्चुअल नहीं था।

यह प्रतिद्वंद्वी है, और आपके कार्यक्रम को इस व्यवहार पर भरोसा नहीं करना चाहिए। इसलिए, एक साक्षर प्रोग्रामर के रूप में, आपको इस तरह के पैटर्न से बचने के लिए उपयोग करना चाहिए और स्कॉट मेयर के दिशानिर्देश का पालन करना चाहिए।

+0

अच्छी तरह से समझाया गया, लेकिन मुझे कहना है कि मुझे यह बहुत सहज लगता है। मैंने सालों से सी ++ में प्रोग्राम किया और बाद में मैंने उन भाषाओं की खोज की जो सबसे व्युत्पन्न कक्षा संस्करण कहलाते थे, मैं चौंक गया और चिल्लाया। – Steve

15

क्या इस दृष्टिकोण में कुछ गड़बड़ है?

Bjarne Stroustrup से उत्तर:

मैं एक निर्माता से एक आभासी फ़ंक्शन को कॉल कर सकते हैं?

हाँ, पर सावधान रहना होगा। ऐसा नहीं हो सकता है कि आप क्या उम्मीद करते हैं। एक निर्माता में, वर्चुअल कॉल तंत्र अक्षम है क्योंकि व्युत्पन्न कक्षाओं से ओवरराइडिंग अभी तक नहीं हुआ है। ऑब्जेक्ट्स बेस अप, "व्युत्पन्न से पहले आधार" से बनाए जाते हैं। पर विचार करें:

#include<string> 
    #include<iostream> 
    using namespace std; 

class B { 
public: 
    B(const string& ss) { cout << "B constructor\n"; f(ss); } 
    virtual void f(const string&) { cout << "B::f\n";} 
}; 

class D : public B { 
public: 
    D(const string & ss) :B(ss) { cout << "D constructor\n";} 
    void f(const string& ss) { cout << "D::f\n"; s = ss; } 
private: 
    string s; 
}; 

int main() 
{ 
    D d("Hello"); 
} 

कार्यक्रम संकलित करता है तथा उत्पादन

B constructor 
B::f 
D constructor 

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

अधिक जानकारी के लिए डी & ई 13.2.4.2 या टीसी ++ पीएल 3 15.4.3 देखें।

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

4

यह अच्छी तरह परिभाषित होने के अर्थ में "ठीक" है। आप जो भी उम्मीद करते हैं उसे करने के अर्थ में यह "ठीक" नहीं हो सकता है।

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

के बाद से इस व्यवहार संभवतः भ्रामक है, यह यह करने के लिए नहीं रखना ही बेहतर है। मैं उस स्थिति में subclassing के बजाय एक वर्ग द्वारा व्यवहार जोड़ना व्यवहार की सिफारिश करेंगे; कक्षा के सदस्यों को कन्स्ट्रक्टर बॉडी के समक्ष बनाया जाता है, और विनाशक के बाद तक चलता है, और इसलिए उन दोनों जगहों पर उपलब्ध हैं।

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

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