C ++ में, मुझे वर्चुअल विधि घोषणा में अंतिम का उपयोग कब करना चाहिए?


11

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

मेरा सवाल अगला है:

क्या कोई उपयोगी मामला है जब मुझे वास्तव finalमें virtualविधि घोषणा में उपयोग करना चाहिए ?


उन सभी कारणों के लिए जो जावा विधियों को अंतिम बनाते हैं?
आयुध

जावा में सभी विधियाँ आभासी हैं। बेस क्लास में अंतिम विधि प्रदर्शन में सुधार कर सकती है। सी ++ में गैर-आभासी तरीके हैं, इसलिए यह इस भाषा के लिए कोई मामला नहीं है। क्या आप किसी अन्य अच्छे मामलों को जानते हैं? मैं उन्हें सुनना चाहूंगा। :)
मेटामेकर

लगता है कि मैं चीजों को समझाने में बहुत अच्छा नहीं हूं - मैं माइक निकी के रूप में सटीक बात कहने की कोशिश कर रहा था।
साधारण जूल

जवाबों:


12

finalकीवर्ड क्या करता है इसका एक पुनर्कथन : मान लीजिए कि हमारे पास आधार वर्ग Aऔर व्युत्पन्न वर्ग है B। फ़ंक्शन f()को इस Aरूप में घोषित किया जा सकता है virtual, जिसका अर्थ है कि वर्ग Bइसे ओवरराइड कर सकता है। लेकिन तब वर्ग की Bइच्छा हो सकती है कि कोई भी वर्ग जो आगे से निकला है, Bको ओवरराइड करने में सक्षम नहीं होना चाहिए f()। यही कारण है कि जब हम घोषणा करने के लिए की जरूरत है f()के रूप में finalमें Bfinalकीवर्ड के बिना , एक बार जब हम एक विधि को आभासी के रूप में परिभाषित करते हैं, तो कोई भी व्युत्पन्न वर्ग इसे ओवरराइड करने के लिए स्वतंत्र होगा। finalकीवर्ड इस स्वतंत्रता को समाप्त कर दिया करने के लिए प्रयोग किया जाता है।

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


सबसे पहले, उत्तर के लिए धन्यवाद, लेकिन क्या यह स्पष्ट नहीं है कि मैंने अपने प्रश्न के पहले वाक्य में क्या लिखा है? जब मुझे वास्तव में एक केस की आवश्यकता होती है class B might wish that any class which is further derived from B should not be able to override f()? क्या कोई वास्तविक दुनिया का मामला है जहां ऐसी कक्षा बी और विधि च () मौजूद है और अच्छी तरह से फिट बैठता है?
मेटामेकर

हां, मुझे माफ करना, फांसी पर लटका देना, मैं अभी एक उदाहरण लिख रहा हूं।
माइक नाकिस

@metamaker वहाँ तुम जाओ।
माइक नाकिस

1
वास्तव में अच्छा उदाहरण, यह अब तक का सबसे अच्छा जवाब है। धन्यवाद!
मेटेमेकर

1
फिर समय बर्बाद करने के लिए धन्यवाद। मुझे इंटरनेट पर एक अच्छा उदाहरण नहीं मिला, बहुत समय बर्बाद किया। मैं जवाब के लिए +1 रखूंगा लेकिन कम प्रतिष्ठा के कारण मैं ऐसा नहीं कर सकता। :( शायद बाद में।
मेटामेकर

2

यह अक्सर डिजाइन के नजरिए से उपयोगी होता है क्योंकि यह अपरिवर्तित चीजों को चिह्नित करने में सक्षम होता है। उसी तरह से constसंकलक गार्ड प्रदान करता है और इंगित करता है कि एक राज्य को बदलना नहीं चाहिए, finalयह इंगित करने के लिए इस्तेमाल किया जा सकता है कि व्यवहार को वंशानुक्रम पदानुक्रम से आगे नहीं बदलना चाहिए।

उदाहरण

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

class Vehicle
{
public:
    virtual ~Vehicle {}

    bool transport(const Location& location)
    {
        // Mandatory check performed for all vehicle types. We could potentially
        // throw or assert here instead of returning true/false depending on the
        // exceptional level of the behavior (whether it is a truly exceptional
        // control flow resulting from external input errors or whether it's
        // simply a bug for the assert approach).
        if (valid_location(location))
            return travel_to(location);

        // If the location is not valid, no vehicle type can go there.
        return false;
    }

private:
    // Overridden by vehicle types. Note that private access here
    // does not prevent derived, nonfriends from being able to override
    // this function.
    virtual bool travel_to(const Location& location) = 0;
};

अब हम कहते हैं कि हमारे खेल में हमारे पास उड़ने वाले वाहन हैं, और कुछ ऐसा है जो सभी उड़ने वाले वाहनों की आवश्यकता है और आम तौर पर यह है कि उन्हें टेक-ऑफ से पहले हैंगर के अंदर एक सुरक्षा निरीक्षण जांच से गुजरना चाहिए।

यहां हम यह finalगारंटी देने के लिए उपयोग कर सकते हैं कि सभी उड़ने वाले वाहन इस तरह के निरीक्षण से गुजरेंगे और उड़ान वाहनों की इस डिजाइन आवश्यकता को भी बताएंगे।

class FlyingVehicle: public Vehicle
{
private:
    bool travel_to(const Location& location) final
    {
        // Mandatory check performed for all flying vehicle types.
        if (safety_inspection())
            return fly_to(location);

        // If the safety inspection fails for a flying vehicle, 
        // it will not be allowed to fly to the location.
        return false;
    }

    // Overridden by flying vehicle types.
    virtual void safety_inspection() const = 0;
    virtual void fly_to(const Location& location) = 0;
};

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

यह उपयोग करने का एक ऐसा उदाहरण है final। ऐसे कई प्रसंग हैं जिनका आप सामना करेंगे, जहां किसी आभासी सदस्य के कार्य के लिए इसका कोई अर्थ नहीं है कि इसे और आगे ले जाया जाए - ऐसा करने से भंगुर डिजाइन और आपके डिजाइन आवश्यकताओं का उल्लंघन हो सकता है।

यही वह जगह finalहै जहाँ एक डिज़ाइन / वास्तु के दृष्टिकोण से उपयोगी है।

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

सवाल

टिप्पणियों से:

अंतिम और आभासी कभी एक ही समय में क्यों उपयोग किए जाएंगे?

यह दोनों के रूप में एक समारोह घोषित करने के लिए एक पदानुक्रम की जड़ में एक आधार वर्ग के लिए कोई मतलब नहीं है virtualऔर final। यह मेरे लिए काफी मूर्खतापूर्ण लगता है, क्योंकि यह संकलक और मानव पाठक दोनों को अनावश्यक हुप्स के माध्यम से कूदना पड़ता है, जो virtualइस तरह के मामले में सीधे तौर पर बचने से बचा जा सकता है । हालाँकि, उपवर्गों को आभासी सदस्य कार्य विरासत में मिलते हैं:

struct Foo
{
   virtual ~Foo() {}
   virtual void f() = 0;
};

struct Bar: Foo
{
   /*implicitly virtual*/ void f() final {...}
};

इस स्थिति में, Bar::fवर्चुअल कीवर्ड का स्पष्ट रूप से उपयोग किया जाना है या नहीं , Bar::fयह एक वर्चुअल फंक्शन है। virtualकीवर्ड तो इस मामले में वैकल्पिक हो जाता है। तो यह समझ के लिए कर सकता है Bar::fके रूप में निर्दिष्ट किया जा करने के लिए finalहै, भले ही यह एक आभासी समारोह है ( finalकर सकते हैं केवल आभासी कार्यों के लिए इस्तेमाल किया जा)।

और कुछ लोग पसंद कर सकते हैं, स्टाइलिस्टली, स्पष्ट रूप से इंगित करने के लिए कि Bar::fआभासी है, जैसे:

struct Bar: Foo
{
   virtual void f() final {...}
};

मेरे लिए यह अनावश्यक दोनों का उपयोग करने की तरह है virtualऔर finalइस संदर्भ (वैसे ही में एक ही कार्य के लिए विनिर्देशक virtualऔर override), लेकिन यह इस मामले में शैली की बात है। कुछ लोगों को लग सकता है कि virtualयहां कुछ मूल्यवान संचार होता है, जैसे externबाहरी लिंकेज के साथ फ़ंक्शन घोषणाओं के लिए उपयोग करना (भले ही यह अन्य लिंकेज क्वालीफायर का अभाव हो)।


आप privateअपनी Vehicleकक्षा के अंदर विधि को ओवरराइड करने की योजना कैसे बनाते हैं ? क्या आपको protectedइसके बजाय मतलब नहीं था ?
एंडी

3
@DavidPacker ओवरराइडिंग private(थोड़ा काउंटर-सहज ज्ञान युक्त) तक विस्तारित नहीं होता है। वर्चुअल फ़ंक्शंस के लिए सार्वजनिक / संरक्षित / निजी विनिर्देशक केवल कॉलर्स पर लागू होते हैं और ओवरराइडर्स के लिए नहीं, गंभीर रूप से लगाए जाते हैं। एक व्युत्पन्न वर्ग अपनी दृश्यता की परवाह किए बिना अपने आधार वर्ग से आभासी कार्यों को ओवरराइड कर सकता है।

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

मैंने सोचा था कि इसे निजी तौर पर सेट करना भी संकलित नहीं होगा। उदाहरण के लिए न तो जावा और न ही सी # की अनुमति दें। सी ++ मुझे हर दिन आश्चर्यचकित करती है। प्रोग्रामिंग के 10 साल बाद भी।
एंडी

जब मैंने पहली बार सामना किया तो @DavidPacker ने मुझे भी पीछे कर दिया। शायद मुझे protectedदूसरों को भ्रमित करने से बचने के लिए इसे बनाना चाहिए । मैंने कम से कम एक टिप्पणी डालते हुए बताया कि कैसे निजी आभासी कार्यों को अभी भी ओवरराइड किया जा सकता है।

0
  1. यह बहुत सारे अनुकूलन को सक्षम करता है, क्योंकि यह संकलन समय पर जाना जा सकता है जिसे फ़ंक्शन कहा जाता है।

  2. "कोड गंध" शब्द को इधर-उधर फेंकने वाले सावधान रहें। "अंतिम" वर्ग का विस्तार करना असंभव नहीं बनाता है। "अंतिम" शब्द पर डबल क्लिक करें, बैकस्पेस हिट करें और कक्षा का विस्तार करें। HOWEVER फ़ाइनल एक उत्कृष्ट दस्तावेज है जिसे डेवलपर आपसे फ़ंक्शन को ओवरराइड करने की उम्मीद नहीं करता है, और इस वजह से कि अगले डेवलपर को बहुत सावधान रहना चाहिए, क्योंकि यदि अंतिम विधि उस तरह से ओवरराइड हो जाती है तो क्लास ठीक से काम करना बंद कर सकती है।


एक ही समय में क्यों finalऔर virtualकभी इस्तेमाल किया जाएगा?
रॉबर्ट हार्वे

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

@ रोबर्टहेयर एबीआई स्थिरता। किसी अन्य मामले में, यह संभवतः होना चाहिए finalऔर override
डेडुप्लिकेटर

@metamaker मैं वही क्यू था, यहाँ टिप्पणियों में चर्चा की गई - प्रोग्रामर.स्टैकएक्सचेंज .com/questions/256778/… - & funnily ने कई अन्य चर्चाओं में ठोकर खाई है, केवल पूछने के बाद से! मूल रूप से, यह इस बारे में है कि यदि एक अनुकूलन करने वाला कंपाइलर / लिंकर यह निर्धारित कर सकता है कि क्या संदर्भ / सूचक एक व्युत्पन्न वर्ग का प्रतिनिधित्व कर सकता है और इस तरह एक आभासी कॉल की आवश्यकता है। यदि विधि (या वर्ग) को स्पष्ट रूप से संदर्भ के स्वयं के स्थैतिक प्रकार से परे ओवरराइड नहीं किया जा सकता है, तो वे सीधे विचलन कर सकते हैं: सीधे कॉल करें। यदि कोई संदेह है - जैसा कि अक्सर होता है - उन्हें वस्तुतः कॉल करना चाहिए। घोषणा finalवास्तव में उन्हें मदद कर सकते हैं
underscore_d
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.