Care este scopul cuvântului cheie „final” în C++11 pentru funcții? (Programare, C++, C++11, Final)

lezebulon a intrebat.

Care este scopul cuvântului final în C++11 pentru funcții? Înțeleg că previne suprascrierea funcțiilor de către clasele derivate, dar dacă acesta este cazul, atunci nu este suficient să declarați ca fiind non-virtuale funcțiile dvs. final funcții? Mai este un alt lucru care îmi scapă aici?

Comentarii

    31

  • nu este suficient să vă declarați funcțiile „finale” ca fiind non-virtuale?” Nu, funcțiile de suprascriere sunt implicit virtuale, indiferent dacă folosiți virtual sau nu. –  > Por ildjarn.
  • @ildjarn asta nu este adevărat dacă nu au fost declarate ca fiind virtuale în superclasa, nu poți deriva dintr-o clasă și transforma o metodă non-virtuală într-una virtuală… –  > Por Dan O.
  • @DanO cred că nu poți suprascrie, dar poți „ascunde” o metodă în acest fel… ceea ce duce la multe probleme, deoarece oamenii nu intenționează să ascundă metode. –  > Por Alex Kremer.
  • 16

  • @DanO : Dacă nu este virtuală în superclasa, atunci nu ar fi vorba de o „suprascriere”. –  > Por ildjarn.
  • Din nou, „overriding” are un înțeles specific aici, și anume acela de a da un comportament polimorf la o funcție virtuală. În exemplul dvs. func nu este virtuală, deci nu există nimic de suprascris și, prin urmare, nimic de marcat ca fiind override sau final. –  > Por ildjarn.
10 răspunsuri
David Rodríguez – dribeas

Ceea ce vă lipsește, așa cum idljarn a menționat deja într-un comentariu, este că dacă sunteți suprascrieți o funcție dintr-o clasă de bază, atunci nu este posibil să o marcați ca fiind non-virtuală:

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!
};

Comentarii

  • Mulțumesc! acesta este punctul pe care îl omiteam : adică faptul că și clasele dvs. de „frunză” trebuie să își marcheze funcția ca fiind virtuală chiar dacă intenționează să suprascrie funcții și nu să fie suprascrise ele însele –  > Por lezebulon.
  • @lezebulon: Clasele dvs. de frunză nu trebuie să marcheze o funcție ca fiind virtuală dacă superclasa a declarat-o ca fiind virtuală. –  > Por Dan O.
  • Metodele din clasele frunză sunt implicit virtuale dacă sunt virtuale în clasa de bază. Cred că compilatoarele ar trebui să avertizeze dacă acest „virtual” implicit lipsește. –  > Por Aaron McDaid.
  • @AaronMcDaid: Compilatoarele avertizează de obicei asupra codului care, fiind corect, ar putea provoca confuzii sau erori. Nu am văzut niciodată pe cineva surprins de această caracteristică particulară a limbajului într-un mod care să poată cauza vreo problemă, așa că nu știu cu adevărat cât de utilă ar putea fi această eroare. Dimpotrivă, uitarea virtual poate cauza erori, iar C++11 a adăugat funcția override la o funcție care va detecta această situație și nu va reuși să compileze atunci când o funcție care este menită să suprascrie este de fapt ascunde –  > Por David Rodríguez – dribeas.
  • Din notele de modificare GCC 4.9: „Noul modul de analiză a moștenirii de tip îmbunătățind devirtualizarea. Devirtualizarea ia acum în considerare spațiile de nume anonime și cuvântul cheie final C++11” – deci nu este doar zahăr sintactic, ci are și un potențial beneficiu de optimizare. –  > Por kfsone.
Nawaz
  • Aceasta are rolul de a împiedica moștenirea unei clase. Adus de la Wikipedia:

    C++11 adaugă, de asemenea, posibilitatea de a împiedica moștenirea din clase sau pur și simplu de a împiedica suprascrierea metodelor în clasele derivate. Acest lucru se face cu ajutorul identificatorului special final. De exemplu:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
    
  • Acesta este utilizat și pentru a marca o funcție virtuală astfel încât să împiedice suprascrierea acesteia în clasele derivate:

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

Wikipedia mai precizează un aspect interesant:

Rețineți că nici override nici final sunt cuvinte-cheie de limbă. Acestea sunt, din punct de vedere tehnic, identificatori; ele capătă o semnificație specială doar atunci când sunt utilizate în acele contexte specifice. În orice altă locație, ele pot fi identificatori valabili.

Asta înseamnă că sunt permise următoarele:

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

Comentarii

  • Mulțumesc, dar am uitat să menționez că întrebarea mea se referea la utilizarea lui „final” în cazul metodelor –  > Por lezebulon.
  • Ai menționat-o @lezebulon 🙂 „care este scopul cuvântului cheie „final” în C++11 pentru funcții„. (sublinierea mea) –  > Por Aaron McDaid.
  • Ai editat-o? Nu văd niciun mesaj care să spună „editat acum x minute de lezebulon”. Cum s-a întâmplat asta? Poate că l-ai editat foarte repede după ce l-ai trimis? –  > Por Aaron McDaid.
  • @Aaron : Modificările făcute în mai puțin de cinci minute de la postare nu sunt reflectate în istoricul revizuirilor. –  > Por ildjarn.
  • @Nawaz: de ce nu sunt cuvinte cheie doar specificatori? Este din motive de compatibilitate înseamnă că este posibil ca codul preexistent înainte de C++11 să folosească final & override în alte scopuri? –  > Por Destructor.
chris green

„final” permite, de asemenea, o optimizare a compilatorului pentru a ocoli apelul indirect:

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

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

  void Blah( void ) { DoSomething(); }

};

cu „final”, compilatorul poate apela CDerived::DoSomething() direct din interiorul Blah(), , sau chiar în linie. Fără el, trebuie să genereze un apel indirect în interiorul lui Blah() deoarece Blah() ar putea fi apelat în interiorul unei clase derivate care a suprascris DoSomething().

Mario Knezović

Nimic de adăugat la aspectele semantice ale „final”.

Dar aș dori să adaug la comentariul lui Chris Green că „final” ar putea deveni un element foarte important tehnică de optimizare a compilatorului în viitorul nu foarte îndepărtat. Nu numai în cazul simplu pe care l-a menționat, ci și pentru ierarhiile de clase mai complexe din lumea reală, care pot fi „închise” prin „final”, permițând astfel compilatorilor să genereze un cod de dispecerizare mai eficient decât în cazul abordării obișnuite cu vtable.

Un dezavantaj cheie al vtables este că, pentru orice obiect virtual de acest tip (presupunând 64 de biți pe un procesor Intel tipic), doar pointerul consumă 25% (8 din 64 de octeți) dintr-o linie de cache. În tipul de aplicații pe care îmi place să le scriu, acest lucru doare foarte tare. (Și, din experiența mea, acesta este argumentul nr. 1 împotriva C++ din punct de vedere purist al performanței, adică de către programatorii C).

În aplicațiile care necesită o performanță extremă, ceea ce nu este atât de neobișnuit pentru C++, acest lucru ar putea deveni într-adevăr minunat, nefiind necesară rezolvarea manuală a acestei probleme în stil C sau jonglări ciudate cu Template-uri.

Această tehnică este cunoscută sub numele de Devirtualizare. Un termen care merită reținut. 🙂

Există un discurs recent foarte bun al lui Andrei Alexandrescu care explică destul de bine cum puteți rezolva astfel de situații astăzi și cum „final” ar putea face parte din rezolvarea unor cazuri similare „automat” în viitor (discutat cu ascultătorii):

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

Comentarii

  • Știe cineva vreun compilator care să folosească aceste lucruri acum ? –  > Por Vincent Fourmond.
  • același lucru pe care vreau să îl spun și eu. –  > Por crazii.
Aaron McDaid

Final nu poate fi aplicat funcțiilor non-virtuale.

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

Nu ar avea prea mult sens să poți marca o metodă non-virtuală ca fiind „final”. Având în vedere

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() va apela întotdeauna A::foo.

Dar, dacă A::foo a fost virtual, , atunci B::foo o va suprascrie. Acest lucru ar putea fi nedorit și, prin urmare, ar fi logic ca funcția virtuală să fie finală.

Totuși, întrebarea este următoarea, de ce permiteți final pentru funcțiile virtuale. Dacă aveți o ierarhie profundă:

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 */ };

Atunci funcția final pune un „prag” la cât de multe suprapuneri pot fi făcute. Alte clase pot extinde A și B și pot suprascrie funcțiile lor foo, dar dacă o clasă extinde C, atunci acest lucru nu este permis.

Prin urmare, probabil că nu are sens să faci ca foo de „nivel superior” să aibă un caracter de „nivel superior”. final, , dar ar putea avea sens la un nivel inferior.

(Cred totuși că există loc pentru a extinde cuvintele final și override la membrii non-virtuali. Acestea ar avea totuși un înțeles diferit).

Comentarii

  • mulțumesc pentru exemplu, este ceva de care nu eram sigur. Dar totuși : ce rost are să ai o funcție finală (și virtuală)? Practic, nu ați putea niciodată să folosiți faptul că funcția este virtuală, deoarece nu poate fi suprascrisă.  > Por lezebulon.
  • @lezebulon, am editat întrebarea mea. Dar apoi am observat răspunsul lui DanO – este un răspuns bun și clar la ceea ce am încercat să spun. –  > Por Aaron McDaid.
  • Nu sunt un expert, dar simt că, uneori, ar putea avea sens să faci o funcție de nivel superior final. De exemplu, dacă știți că doriți ca toate Shapes să foo()-ceva predefinit și definit pe care nici o formă derivată nu ar trebui să-l modifice. Sau mă înșel și există un model mai bun de utilizat pentru acest caz? EDIT: Oh, poate pentru că, în acest caz, pur și simplu nu ar trebui să se facă un nivel superior foo() virtual pentru început? Dar, totuși, poate fi ascunsă, chiar dacă este apelată corect (polimorfic) prin intermediul Shape*… –  > Por slackwing.
YoungJohn

Un caz de utilizare a cuvântului cheie „final” care îmi place foarte mult este următorul:

// 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 */}
};

Comentarii

  • Da, acesta este, în esență, un exemplu de model de metodă șablon. Și înainte de C++11, TMP a fost întotdeauna cel care m-a făcut să-mi doresc ca C++ să aibă o caracteristică de limbaj precum „final”, așa cum a avut Java. –  > Por Kaitain.
Kerrek SB

final adaugă o intenție explicită de a nu avea o funcție suprascrisă și va cauza o eroare de compilare în cazul în care acest lucru este încălcat:

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

În forma actuală, codul se compilează, iar B::foo suprascrie A::foo. B::foo este, de asemenea, virtuală, apropo. Cu toate acestea, dacă schimbăm #1 în virtual int foo() final, , atunci aceasta este o eroare de compilare și nu avem voie să suprascriem A::foo mai departe în clasele derivate.

Rețineți că acest lucru nu ne permite să „redeschidem” o nouă ierarhie, adică nu există nicio modalitate de a face ca B::foo o funcție nouă, fără legătură între ele, care să poată fi independent în fruntea unei noi ierarhii virtuale. Odată ce o funcție este finală, nu mai poate fi declarată niciodată în nicio clasă derivată.

Dan O

Cuvântul cheie final vă permite să declarați o metodă virtuală, să o suprascrieți de N ori și apoi să impuneți că „aceasta nu mai poate fi suprascrisă”. Ar fi util pentru a restricționa utilizarea clasei derivate, astfel încât să puteți spune „Știu că superclasa mea vă permite să suprascrieți acest lucru, dar dacă doriți să derivați din mine, nu puteți!”.

struct Foo
{
   virtual void DoStuff();
}

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

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

După cum au subliniat și alte postere, nu se poate aplica funcțiilor non-virtuale.

Unul dintre scopurile cuvântului cheie final este de a preveni suprascrierea accidentală a unei metode. În exemplul meu, DoStuff() ar fi putut fi o funcție de ajutor pe care clasa derivată trebuie pur și simplu să o redenumească pentru a obține un comportament corect. Fără final, eroarea nu ar fi fost descoperită până la testare.

Krishna Ganeriwal

Cuvântul cheie Final din C++, atunci când este adăugat la o funcție, împiedică ca aceasta să fie suprascrisă de o clasă de bază.De asemenea, atunci când este adăugat la o clasă, împiedică moștenirea oricărui tip.Luați în considerare următorul exemplu care arată utilizarea specificatorului final. Acest program eșuează la compilare.

#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
";
  }
};

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

De asemenea:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

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

crazii

Completare la răspunsul lui Mario 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();

Codul de mai sus arată teoria, dar nu a fost testat efectiv pe compilatoare reale. Foarte apreciat dacă cineva lipește o ieșire dezasamblată.

Tags:, ,