कार्यों के लिए C ++ 11 में "अंतिम" कीवर्ड का उद्देश्य क्या है?


143

finalकार्यों के लिए C ++ 11 में कीवर्ड का उद्देश्य क्या है ? मैं समझता हूं कि यह व्युत्पन्न वर्गों द्वारा कार्य करने से रोकता है, लेकिन यदि यह मामला है, तो क्या यह गैर-आभासी आपके finalकार्यों को घोषित करने के लिए पर्याप्त नहीं है ? क्या कोई और चीज़ है जो मुझे यहाँ याद आ रही है?


30
" गैर-आभासी को आपके" अंतिम "फ़ंक्शन के रूप में घोषित करने के लिए पर्याप्त नहीं है " नहीं, ओवरराइडिंग फ़ंक्शन अंतर्निहित रूप से आभासी हैं कि आप virtualकीवर्ड का उपयोग करते हैं या नहीं।
१।

13
@ चिल्ड्रन यह सच नहीं है अगर उन्हें सुपर क्लास में वर्चुअल घोषित नहीं किया गया, तो आप क्लास से नहीं निकल सकते और नॉन-वर्चुअल तरीके को वर्चुअल में बदल सकते हैं ..
Dan O

10
@ डानो मुझे लगता है कि आप ओवरराइड नहीं कर सकते लेकिन आप इस तरह से एक विधि को "छिपा" सकते हैं, जो कई समस्याओं को जन्म देती है क्योंकि लोगों को तरीकों को छिपाने का मतलब नहीं है।
एलेक्स क्रेमर

16
@ डानो: यदि यह सुपर क्लास में वर्चुअल नहीं है तो यह "ओवरराइडिंग" नहीं होगा।
ildjarn

2
फिर, " ओवरराइडिंग " का यहां एक विशिष्ट अर्थ है, जो एक आभासी फ़ंक्शन को बहुरूपी व्यवहार देना है। आपके उदाहरण funcमें आभासी नहीं है, इसलिए ओवरराइड करने के लिए कुछ भी नहीं है और इस प्रकार overrideया के रूप में चिह्नित करने के लिए कुछ भी नहीं है final
iljarn

जवाबों:


129

आप जो याद कर रहे हैं, जैसा कि पहले से ही एक टिप्पणी में उल्लिखित idljarn है कि यदि आप एक आधार वर्ग से किसी फ़ंक्शन को ओवरराइड कर रहे हैं , तो आप संभवतः इसे गैर-आभासी के रूप में चिह्नित नहीं कर सकते हैं:

struct base {
   virtual void f();
};
struct derived : base {
   void f() final;       // virtual as it overrides base::f
};
struct mostderived : derived {
   //void f();           // error: cannot override!
};

धन्यवाद! यह वह बिंदु है जो मुझे याद आ रहा था: यानी कि यहां तक ​​कि आपके "पत्ती" वर्गों को भी अपने कार्य को आभासी के रूप में चिह्नित करने की आवश्यकता है, भले ही वे कार्यों को ओवरराइड करने का इरादा रखते हों, और खुद को ओवरराइड नहीं करने के लिए
lezebulon

8
@lezebulon: आपके पत्ते वर्गों को किसी फ़ंक्शन को वर्चुअल के रूप में चिह्नित करने की आवश्यकता नहीं है यदि सुपर क्लास ने इसे वर्चुअल घोषित किया है।
दान हे

5
पत्ती वर्गों में विधियाँ अंतर्निहित रूप से आभासी हैं यदि वे आधार वर्ग में आभासी हैं। मुझे लगता है कि संकलक को चेतावनी देनी चाहिए कि क्या यह निहित 'आभासी' गायब है।
हारून मैकडैड

@AaronMcDaid: आमतौर पर कंपाइलर कोड के बारे में चेतावनी देते हैं, जो सही होने पर भ्रम या त्रुटियों का कारण हो सकता है। मैंने कभी किसी को भाषा की इस खास विशेषता से आश्चर्यचकित नहीं देखा, जो किसी भी समस्या का कारण हो सकता है, इसलिए मुझे वास्तव में यह नहीं पता कि वह त्रुटि कितनी उपयोगी हो सकती है। इसके विपरीत, भूल virtualकर सकते हैं त्रुटियों का कारण है, और सी ++ 11 जोड़ा overrideएक समारोह के लिए टैग है कि उस स्थिति का पता लगाने और संकलित करने के लिए असफल हो जायेगी जब एक समारोह है कि मतलब है को ओवरराइड वास्तव में खाल
dribeas - डेविड Rodríguez

1
GCC 4.9 से नोट बदलें: "नए प्रकार के वंशानुक्रम विश्लेषण मॉड्यूल में विचलन में सुधार हुआ है। विचलन अब खाते के अनाम नाम-स्थान और C ++ 11 अंतिम कीवर्ड को ध्यान में रखता है" - इसलिए यह सिंटैक्टिक शुगर नहीं है, इसका एक संभावित अनुकूलन लाभ भी है।
केफ्सोन

127
  • यह एक वर्ग को विरासत में मिलने से रोकना है। से विकिपीडिया :

    C ++ 11 भी वर्गों से विरासत को रोकने या केवल व्युत्पन्न वर्गों में ओवरराइडिंग विधियों को रोकने की क्षमता जोड़ता है। यह विशेष पहचानकर्ता फाइनल के साथ किया जाता है। उदाहरण के लिए:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
  • इसका उपयोग किसी वर्चुअल फ़ंक्शन को चिह्नित करने के लिए भी किया जाता है ताकि इसे व्युत्पन्न वर्गों में अतिरंजित होने से रोका जा सके:

    struct Base2 {
        virtual void f() final;
    };
    
    struct Derived2 : Base2 {
        void f(); // ill-formed because the virtual function Base2::f has 
                  // been marked final
    };

विकिपीडिया आगे एक दिलचस्प बिंदु बनाता है :

नोट है कि न तो overrideहै और न ही finalभाषा कीवर्ड हैं। वे तकनीकी रूप से पहचानकर्ता हैं; वे केवल विशिष्ट अर्थ प्राप्त करते हैं जब उन विशिष्ट संदर्भों में उपयोग किया जाता हैकिसी अन्य स्थान पर, वे वैध पहचानकर्ता हो सकते हैं।

इसका मतलब है, निम्नलिखित की अनुमति है:

int const final = 0;     // ok
int const override = 1;  // ok

1
धन्यवाद, लेकिन मैं यह उल्लेख करना भूल गया कि मेरे प्रश्न में तरीकों के साथ "अंतिम" के उपयोग का संबंध है
lezebulon

आपने इसका उल्लेख @lezebulon :-) " कार्यों के लिए C ++ 11 में" अंतिम "कीवर्ड के उद्देश्य से क्या किया है "। (मेरा जोर)
हारून मैकडैड

आपने इसे संपादित किया? मुझे कोई भी संदेश नहीं दिखाई देता है जो कहता है "lezebulon द्वारा x मिनट पहले संपादित"। ये कैसे हो गया? हो सकता है कि आपने इसे सबमिट करने के बाद बहुत तेज़ी से इसे संपादित किया हो?
हारून मैकडैड

5
@ एरॉन: पोस्टिंग के पांच मिनट के भीतर किए गए संपादन पुनरीक्षण इतिहास में परिलक्षित नहीं होते हैं।
०१

@ नवाज़: वे कीवर्ड सिर्फ स्पेसियर क्यों नहीं हैं? क्या यह संगतता कारणों के कारण है इसका मतलब यह है कि C ++ 11 से पहले अन्य प्रयोजनों के लिए preexisting कोड अंतिम और ओवरराइड का उपयोग करता है?
विध्वंसक

45

"अंतिम" अप्रत्यक्ष कॉल को बायपास करने के लिए एक कंपाइलर अनुकूलन की भी अनुमति देता है:

class IAbstract
{
public:
  virtual void DoSomething() = 0;
};

class CDerived : public IAbstract
{
  void DoSomething() final { m_x = 1 ; }

  void Blah( void ) { DoSomething(); }

};

"फाइनल" के साथ, कंपाइलर CDerived::DoSomething()सीधे भीतर से कॉल कर सकता है Blah(), या इनलाइन भी कर सकता है। इसके बिना, इसे अंदर एक अप्रत्यक्ष कॉल उत्पन्न करना होगा Blah()क्योंकि Blah()एक व्युत्पन्न वर्ग के अंदर बुलाया जा सकता है जिसने ओवरराइड किया है DoSomething()


29

"अंतिम" के शब्दार्थ पहलुओं में जोड़ने के लिए कुछ भी नहीं है।

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

Vtables का एक प्रमुख नुकसान यह है कि ऐसी किसी भी आभासी वस्तु के लिए (एक विशिष्ट इंटेल सीपीयू पर 64-बिट्स को संभालने के लिए) सूचक अकेले कैश लाइन के 25% (64 बाइट्स में से 8) खाता है। जिस तरह के एप्लिकेशन में मुझे लिखने में मजा आता है, यह बहुत बुरा लगता है। (और मेरे अनुभव से यह सी 1 + के खिलाफ एक शुद्ध प्रदर्शन के दृष्टिकोण से सी तर्क है, अर्थात सी प्रोग्रामर द्वारा।)

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

इस तकनीक को देवतीकरण के रूप में जाना जाता है । याद रखने लायक शब्द। :-)

आंद्रेई अलेक्जेंड्रेस्कु द्वारा हाल ही में एक शानदार भाषण दिया गया है, जो बताता है कि आप आज ऐसी स्थितियों को कैसे हल कर सकते हैं और "अंतिम" भविष्य में इसी तरह के मामलों को "स्वचालित रूप से" हल करने का हिस्सा हो सकता है (श्रोताओं के साथ चर्चा की गई):

http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly


23
?४ का २५% है?
iljarn

6
किसी को एक संकलक के बारे में पता है जो अब उन का उपयोग करता है?
विंसेंट फोरमंड

वही बात जो मैं कहना चाहता हूं।
crazii

8

अंतिम गैर-आभासी कार्यों पर लागू नहीं किया जा सकता है।

error: only virtual member functions can be marked 'final'

गैर-आभासी पद्धति को 'अंतिम' के रूप में चिह्नित करने में सक्षम होना बहुत सार्थक नहीं होगा। दिया हुआ

struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // this will call A :: foo anyway, regardless of whether there is a B::foo

a->foo()हमेशा फोन करेंगे A::foo

लेकिन, अगर A :: foo था virtual, तो B :: foo इसे ओवरराइड कर देगा। यह अवांछनीय हो सकता है, और इसलिए यह वर्चुअल फ़ंक्शन को अंतिम बनाने के लिए समझ में आएगा।

सवाल यह है कि आभासी कार्यों पर अंतिम अनुमति क्यों दी जाती है। यदि आपके पास एक गहरी पदानुक्रम है:

struct A            { virtual void foo(); };
struct B : public A { virtual void foo(); };
struct C : public B { virtual void foo() final; };
struct D : public C { /* cannot override foo */ };

फिर finalओवरराइडिंग कितनी हो सकती है, इस पर एक 'मंजिल' डालता है। अन्य वर्ग ए और बी का विस्तार कर सकते हैं और उनके ओवरराइड कर सकते हैं foo, लेकिन यह एक वर्ग सी का विस्तार करता है तो इसकी अनुमति नहीं है।

तो यह शायद 'टॉप-लेवल' फू finalबनाने के लिए समझ में नहीं आता है, लेकिन यह समझ कम कर सकता है।

(मुझे लगता है कि हालांकि, शब्दों को अंतिम रूप देने और गैर-आभासी सदस्यों को ओवरराइड करने के लिए जगह है। हालांकि उनका एक अलग अर्थ होगा)।


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

@lezebulon, मैंने अपना प्रश्न संपादित किया। लेकिन फिर मैंने दानो के जवाब पर गौर किया - यह एक स्पष्ट स्पष्ट जवाब है कि मैं क्या कहना चाह रहा था।
हारून मैकडैड

मैं एक विशेषज्ञ नहीं हूं, लेकिन मुझे लगता है कि कभी-कभी यह एक शीर्ष-स्तरीय समारोह बनाने के लिए समझ में आता है final। उदाहरण के लिए, यदि आप जानते हैं कि आप सभी चाहते हैं Shapeके लिए रों foo()पूर्वनिर्धारित और निश्चित -something कि कोई व्युत्पन्न आकार संशोधित करना चाहिए। या, क्या मैं गलत हूं और उस मामले के लिए नियोजित करने के लिए एक बेहतर पैटर्न है? संपादित करें: ओह, हो सकता है क्योंकि उस मामले में, किसी को बस शीर्ष स्तर foo() virtualके साथ शुरू करने के लिए नहीं बनाना चाहिए ? लेकिन फिर भी, इसे छिपाया जा सकता है, भले ही इसे सही ढंग से (पॉलीमॉर्फिक रूप से) कहा जाता हो Shape*...
एंड्रयू च्योंग

8

'अंतिम' कीवर्ड के लिए एक उपयोग-केस जो मैं शौकीन हूं, वह इस प्रकार है:

// This pure abstract interface creates a way
// for unit test suites to stub-out Foo objects
class FooInterface
{
public:
   virtual void DoSomething() = 0;
private:
   virtual void DoSomethingImpl() = 0;
};

// Implement Non-Virtual Interface Pattern in FooBase using final
// (Alternatively implement the Template Pattern in FooBase using final)
class FooBase : public FooInterface
{
public:
    virtual void DoSomething() final { DoFirst(); DoSomethingImpl(); DoLast(); }
private:
    virtual void DoSomethingImpl() { /* left for derived classes to customize */ }
    void DoFirst(); // no derived customization allowed here
    void DoLast(); // no derived customization allowed here either
};

// Feel secure knowing that unit test suites can stub you out at the FooInterface level
// if necessary
// Feel doubly secure knowing that your children cannot violate your Template Pattern
// When DoSomething is called from a FooBase * you know without a doubt that
// DoFirst will execute before DoSomethingImpl, and DoLast will execute after.
class FooDerived : public FooBase
{
private:
    virtual void DoSomethingImpl() {/* customize DoSomething at this location */}
};

1
हां, यह अनिवार्य रूप से टेम्पलेट विधि पैटर्न का एक उदाहरण है। और पूर्व-सी ++ 11, यह हमेशा टीएमपी था जो मुझे इच्छा था कि सी ++ में "फाइनल" जैसी भाषा की सुविधा थी, जैसा कि जावा ने किया था।
१४:१५ बजे काइटेन

6

final अपने कार्य को ओवरराइड नहीं करने के लिए एक स्पष्ट इरादा जोड़ता है, और एक संकलक त्रुटि का कारण होगा इसका उल्लंघन किया जाना चाहिए:

struct A {
    virtual int foo(); // #1
};
struct B : A {
    int foo();
};

जैसे ही कोड खड़ा होता है, यह संकलन करता है, और B::fooओवरराइड करता है A::fooB::fooआभासी भी है, वैसे भी। हालाँकि, अगर हम # 1 को बदलते हैं virtual int foo() final, तो यह एक संकलक त्रुटि है, और हमें A::fooव्युत्पन्न वर्गों में किसी भी तरह से ओवरराइड करने की अनुमति नहीं है ।

ध्यान दें कि यह हमें एक नए पदानुक्रम को "फिर से खोलने" की अनुमति नहीं देता है, अर्थात B::fooएक नया, असंबंधित फ़ंक्शन बनाने का कोई तरीका नहीं है जो स्वतंत्र रूप से एक नए आभासी पदानुक्रम के सिर पर हो सकता है। एक बार एक समारोह अंतिम होने के बाद, इसे किसी भी व्युत्पन्न वर्ग में फिर से घोषित नहीं किया जा सकता है।


5

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

struct Foo
{
   virtual void DoStuff();
}

struct Bar : public Foo
{
   void DoStuff() final;
}

struct Babar : public Bar
{
   void DoStuff(); // error!
}

जैसा कि अन्य पोस्टरों ने बताया है, इसे गैर-आभासी कार्यों पर लागू नहीं किया जा सकता है।

अंतिम कीवर्ड का एक उद्देश्य किसी विधि के आकस्मिक ओवरराइडिंग को रोकना है। मेरे उदाहरण में, DoStuff () एक सहायक कार्य हो सकता है कि व्युत्पन्न वर्ग को सही व्यवहार प्राप्त करने के लिए नाम बदलने की आवश्यकता है। अंतिम के बिना, परीक्षण तक त्रुटि की खोज नहीं की जाएगी।


1

किसी फ़ंक्शन में जोड़े जाने पर C ++ में अंतिम कीवर्ड, इसे बेस क्लास द्वारा ओवरराइड होने से रोकता है। जब किसी वर्ग में जोड़ा जाता है तो किसी भी प्रकार की विरासत को रोकता है। निम्नलिखित उदाहरण पर विचार करें जो अंतिम विनिर्देशक के उपयोग को दर्शाता है। यह कार्यक्रम संकलन में विफल रहता है।

#include <iostream>
using namespace std;

class Base
{
  public:
  virtual void myfun() final
  {
    cout << "myfun() in Base";
  }
};
class Derived : public Base
{
  void myfun()
  {
    cout << "myfun() in Derived\n";
  }
};

int main()
{
  Derived d;
  Base &b = d;
  b.myfun();
  return 0;
}

इसके अलावा:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

int main()
{
  Derived d;
  return 0;
}

0

मारियो Knezović के जवाब के लिए पूरक:

class IA
{
public:
  virtual int getNum() const = 0;
};

class BaseA : public IA
{
public:
 inline virtual int getNum() const final {return ...};
};

class ImplA : public BaseA {...};

IA* pa = ...;
...
ImplA* impla = static_cast<ImplA*>(pa);

//the following line should cause compiler to use the inlined function BaseA::getNum(), 
//instead of dynamic binding (via vtable or something).
//any class/subclass of BaseA will benefit from it

int n = impla->getNum();

उपरोक्त कोड सिद्धांत दिखाता है, लेकिन वास्तव में वास्तविक संकलक पर परीक्षण नहीं किया गया है। बहुत सराहना की अगर किसी को एक disassembled उत्पादन पेस्ट।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.