İşlevler için C ++ 11'deki “son” anahtar kelimenin amacı nedir?


143

finalFonksiyonlar için C ++ 11'deki anahtar kelimenin amacı nedir ? Türetilmiş sınıflar tarafından işlev geçersiz kılmayı önlediğini anlıyorum, ancak bu durumda, finalişlevlerinizi sanal olmayan olarak ilan etmek yeterli değil mi? Burada özlediğim başka bir şey var mı?


30
msgstr " " son "işlevlerinizi sanal olmayan olarak bildirmek yeterli değil " Hayır, virtualanahtar kelimeyi kullanıp kullanmadığınız geçersiz kılma işlevleri dolaylı olarak sanaldır .
ildjarn

13
@ildjarn, eğer süper sınıfta sanal olarak ilan edilmemişlerse, bu bir sınıftan türeyemez ve sanal olmayan bir yöntemi sanal bir metoda dönüştüremezsiniz.
Dan O

10
@DanO Ben geçersiz kılınamayacağınızı düşünüyorum ama bir yöntemi bu şekilde "gizleyebilirsiniz" .. insanlar yöntemleri gizlemek anlamına gelmez gibi birçok soruna yol açar.
Alex Kremer

16
@DanO: Süper sınıfta sanal değilse "geçersiz kılma" olmaz.
ildjarn

2
Yine, " geçersiz kılmanın " burada sanal bir fonksiyona polimorfik davranış kazandırmak için özel bir anlamı vardır. Örneğinizde funcsanal değil, bu nedenle geçersiz kılınacak bir şey ve dolayısıyla overrideveya olarak işaretlenecek bir şey yok final.
ildjarn

Yanıtlar:


129

Bir açıklamada daha önce belirtildiği gibi, eksik olan şey, bir temel sınıftan bir işlevi geçersiz kılarsanız , muhtemelen sanal olmayan olarak işaretleyemezsiniz:

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

Teşekkürler! bu benim eksik olduğum nokta: yani "yaprak" sınıflarınız bile işlevleri geçersiz kılmayı düşünüyor olsalar bile işlevlerini sanal olarak işaretlemeli ve kendilerini geçersiz
kılmamalılar

8
@lezebulon: Süper sınıf bunu sanal olarak bildirdiyse, yaprak sınıflarınızın bir işlevi sanal olarak işaretlemesi gerekmez.
Dan O

5
Yaprak sınıflarındaki yöntemler, temel sınıfta sanal olmaları durumunda örtük olarak sanaldır. Bu örtük 'sanal' eksikse derleyicilerin uyarması gerektiğini düşünüyorum.
Aaron McDaid

@AaronMcDaid: Derleyiciler genellikle doğru olduklarında karışıklığa veya hatalara neden olabilecek kod konusunda uyarırlar. Hiç kimsenin dilin bu özelliğine herhangi bir soruna neden olabilecek bir şekilde şaşırdığını görmedim, bu yüzden bu hatanın ne kadar yararlı olabileceğini gerçekten bilmiyorum. Aksine, unutmak virtualhatalara neden olabilir ve C ++ 11, overridebu durumu algılayacak ve geçersiz kılmayı amaçlayan bir işlev gerçekten gizlendiğinde
David Rodríguez - dribeas

1
GCC 4.9 değişiklik notlarından: "Devrileştirmeyi geliştiren yeni tip miras analiz modülü. Devrileştirme artık anonim ad alanlarını ve C ++ 11 son anahtar sözcüğünü dikkate alıyor" - bu yüzden sadece sözdizimsel şeker değil, aynı zamanda potansiyel bir optimizasyon avantajına da sahip.
kfsone

127
  • Bir sınıfın miras alınmasını önlemek içindir. Gönderen Vikipedi :

    C ++ 11 ayrıca sınıflardan miras alınmasını önleme veya türetilmiş sınıflarda geçersiz kılma yöntemlerini önleme yeteneği ekler. Bu özel tanımlayıcı final ile yapılır. Örneğin:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
    
  • Ayrıca, türetilen sınıflarda geçersiz kılınmasını önlemek için sanal bir işlevi işaretlemek için kullanılır:

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

Wikipedia ayrıca ilginç bir noktaya işaret ediyor :

Dil anahtar kelimelerinin ne overrideolmadığını finalda unutmayın . Bunlar teknik olarak tanımlayıcılardır; sadece bu belirli bağlamlarda kullanıldıklarında özel bir anlam kazanırlar . Başka herhangi bir konumda, geçerli tanımlayıcılar olabilirler.

Bu, aşağıdakilere izin verildiği anlamına gelir:

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

1
teşekkürler, ama benim soru yöntemleri ile "final" kullanımı ile ilgili bahsetmeyi unuttum
lezebulon

@Lezebulon :-) " fonksiyonlar için C ++ 11'deki" son "anahtar kelimenin amacı nedir ? (Benim vurgu)
Aaron McDaid

Düzenledin mi? "X dakika önce lezebulon tarafından düzenlendi" mesajını görmüyorum. Bu nasıl oldu? Belki de gönderdikten sonra çok hızlı bir şekilde düzenlediniz?
Aaron McDaid

5
@Aaron: Gönderdikten sonra beş dakika içinde yapılan düzenlemeler düzeltme geçmişine yansıtılmaz.
ildjarn

@Nawaz: Neden anahtar kelimeler yalnızca belirleyici değiller? Uyumluluk nedenlerinden ötürü, C ++ 11'den önce önceden varolan kodun diğer amaçlar için son & geçersiz kılmayı kullanmasının mümkün olduğu anlamına mı geliyor?
Yıkıcı

45

"final" ayrıca bir derleyici optimizasyonunun dolaylı çağrıyı atlamasına izin verir:

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

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

  void Blah( void ) { DoSomething(); }

};

"final" ile derleyici CDerived::DoSomething()doğrudan içeriden Blah(), hatta satır içi çağrı yapabilir . O olmadan, içinde dolaylı çağrıyı oluşturmak için var Blah()olduğundan Blah()geçersiz kılınmış olan bir türetilmiş sınıf içinde denilebilir DoSomething().


29

"Final" in anlamsal yönlerine eklenecek bir şey yok.

Ama chris green'in “final” in o kadar da uzak olmayan bir gelecekte çok önemli bir derleyici optimizasyon tekniği olabileceği yorumuna eklemek istiyorum . Sadece bahsettiği basit durumda değil, aynı zamanda "son" ile "kapatılabilen" daha karmaşık gerçek dünya sınıfı hiyerarşileri için, böylece derleyicilerin normal vtable yaklaşımından daha verimli dağıtım kodu üretmesine izin verir.

Vtables'ın önemli bir dezavantajı, bu tür herhangi bir sanal nesne için (tipik bir Intel CPU üzerinde 64-bit olduğu varsayılarak) yalnızca işaretçinin bir önbellek hattının% 25'ini (64 baytın 8'ini) yemesi. Yazmayı sevdiğim türden uygulamalarda bu çok acı veriyor. (Ve benim tecrübelerime göre, saf bir performans bakış açısıyla, yani C programcıları tarafından C ++ 'a karşı 1 numaralı argüman.)

C ++ için olağandışı olmayan aşırı performans gerektiren uygulamalarda, bu gerçekten harika olabilir, bu sorunu C stili veya garip Şablon hokkabazlığında manuel olarak çözmek zorunda kalmaz.

Bu teknik Devrileştirme olarak bilinir . Hatırlamaya değer bir terim. :-)

Andrei Alexandrescu'nun bugün böyle durumları nasıl çözebileceğinizi ve gelecekte "nihai" benzer vakaları "otomatik olarak" çözmenin bir parçası olabileceğini açıklayan harika bir konuşma var (dinleyicilerle tartışıldı):

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


23
8 64'ün% 25'i?
ildjarn

6
Şimdi bunları kullanan bir derleyici bilen var mı?
Vincent Fourmond

söylemek istediğim aynı şey.
crazii

8

Final, sanal olmayan işlevlere uygulanamaz.

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

Sanal olmayan bir yöntemi 'final' olarak işaretlemek çok anlamlı olmazdı. verilmiş

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()her zaman arayacaktır A::foo.

Ancak, A :: foo olsaydı virtual, B :: foo onu geçersiz kılardı. Bu istenmeyen bir durum olabilir ve bu nedenle sanal işlevi sonlandırmak mantıklı olacaktır.

Soru şu ki, neden sanal işlevlerde final yapılmasına izin veriyoruz. Derin bir hiyerarşiniz varsa:

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

Sonra finalne kadar geçersiz kılmanın yapılabileceğine bir 'zemin' koyar. Diğer sınıflar A ve B'yi genişletip geçersiz kılabilir foo, ancak bir sınıf C'yi genişletir, sonra izin verilmez.

Bu yüzden muhtemelen 'üst düzey' foo finalyapmak mantıklı değil, ama daha düşük olabilir.

(Sanırım son kelimeleri genişletmek ve sanal olmayan üyelere geçersiz kılmak için yer var. Yine de farklı bir anlamı olacaktı.)


örnek için teşekkürler, bu emin olmadığım bir şey. Ama yine de: son (ve sanal) bir işleve sahip olmanın anlamı nedir? Temel olarak, fonksiyonun sanal olduğu gerçeğini asla kullanamazsınız çünkü geçersiz
kılınamaz

@lezebulon, sorumu düzenledim. Ama sonra DanO'nun cevabını fark ettim - söylemeye çalıştığım şeyin iyi bir cevabı.
Aaron McDaid

Ben uzman değilim, ama bazen üst düzey bir işlev yapmanın mantıklı olabileceğini hissediyorum final. Bildiğiniz Örneğin, tüm istemek Shapeiçin lar foo()hiçbir türetilmiş şekil değişiklik yapması gerektiğini önceden ve kesin bir şey. Yoksa yanılıyor muyum ve bu dava için daha iyi bir örnek var mı? EDIT: Oh, belki de bu durumda, sadece foo() virtualbaşlamak için üst düzey yapmak gerekir ? Ama yine de, doğru şekilde (polimorfik olarak) doğru bir şekilde çağrılsa bile gizlenebilir Shape*...
Andrew Cheong

8

Sevdiğim 'nihai' anahtar kelime için bir kullanım örneği aşağıdaki gibidir:

// 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
Evet, bu aslında Şablon Yöntemi Kalıbının bir örneğidir. Ve C ++ 11 öncesi, Java'nın yaptığı gibi C ++ 'ın “final” gibi bir dil özelliğine sahip olmasını isteyen her zaman TMP'ydi.
Kaitain

6

final işlevinizin geçersiz kılınmaması için açık bir niyet ekler ve bu ihlal edildiğinde bir derleyici hatasına neden olur:

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

Kod durduğunda derler ve B::foogeçersiz kılar A::foo. B::foobu arada sanaldır. Ancak, # 1 olarak değiştirirsek virtual int foo() final, bu bir derleyici hatasıdır ve A::footüretilmiş sınıflarda daha fazla geçersiz kılmamıza izin verilmez .

Bunun yeni bir hiyerarşiyi "yeniden açmamıza" izin vermediğini, yani B::foobağımsız olarak yeni bir sanal hiyerarşinin başında olabilecek yeni, ilgisiz bir işlev yapmanın bir yolu olmadığını unutmayın . Bir işlev sonlandığında, türetilmiş herhangi bir sınıfta bir daha asla bildirilemez.


5

Nihai anahtar kelime, sanal bir yöntem bildirmenize, N kez geçersiz kılmanıza ve ardından 'bunun artık geçersiz kılınamayacağına' karar vermenizi sağlar. Türetilmiş sınıfınızın kullanımını kısıtlamakta yararlı olur, böylece "Süper sınıfımın bunu geçersiz kılmanıza izin verdiğini biliyorum, ancak benden türetmek istiyorsanız, yapamazsınız!"

struct Foo
{
   virtual void DoStuff();
}

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

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

Diğer posterlerin de işaret ettiği gibi, sanal olmayan fonksiyonlara uygulanamaz.

Son anahtar kelimenin bir amacı, bir yöntemin yanlışlıkla geçersiz kılınmasını önlemektir. Örneğimde, DoStuff () yöntemi, türetilmiş sınıfın doğru davranışı elde etmek için yeniden adlandırması gereken bir yardımcı işlev olabilir. Son olmadan, hata test edilene kadar bulunamaz.


1

Bir işleve eklendiğinde C ++ 'da son anahtar sözcük, bir temel sınıf tarafından geçersiz kılınmasını önler. Ayrıca bir sınıfa eklendiğinde, herhangi bir türden miras alınmasını önler. Son belirtecin kullanımını gösteren aşağıdaki örneği düşünün. Bu program derlemede başarısız oluyor.

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

Ayrıca:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

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

0

Mario Knezović'in cevabına ek:

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();

Yukarıdaki kod teoriyi gösterir, ancak gerçek derleyiciler üzerinde test edilmemiştir. Herkes demonte bir çıktı yapıştırırsanız çok takdir etmek.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.