web-gelistirme-sc.com

Son sanal işlevin amacı nedir?

Wikipedia , C++ 11 son değiştiricisinde aşağıdaki örneğe sahip:

struct Base2 {
    virtual void f() final;
};

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

Sanal bir işlev sunma ve onu hemen nihai olarak işaretleme noktasını anlamıyorum. Bu sadece kötü bir örnek mi, yoksa dahası mı var?

45
fredoverflow

Genellikle final, temel sınıfın sanal işlev tanımında kullanılmaz. final, başka türetilmiş türlerin işlevi daha fazla geçersiz kılmasını önlemek için işlevi geçersiz kılan türetilmiş bir sınıf tarafından kullanılacaktır. Geçersiz kılma işlevi normalde sanal olması gerektiğinden, bu işlevi başka türetilmiş bir türde herkesin bu işlevi geçersiz kılabileceği anlamına gelir. final, birinin bir başkasını geçersiz kılan ancak kendiliğinden geçersiz kılınamayan bir işlev belirtmesine izin verir.

Örneğin, bir sınıf hiyerarşisi tasarlıyorsanız ve bir işlevi geçersiz kılmanız gerekiyorsa, ancak sınıf hiyerarşisi kullanıcılarının aynı şeyi yapmasına izin vermek istemiyorsanız, işlevleri türetilmiş sınıflarınızda son olarak işaretleyebilirsiniz. 

51
bames53

Bana hiç faydalı görünmüyor. Bence bu sadece sözdizimini gösteren bir örnek.

Muhtemel bir kullanım, f'nin gerçekten geçersiz kılınmasını istemiyorsanız, ancak yine de bir kararsızlık oluşturmak istiyorsanız, ancak bu hala işlerin yapılması için korkunç bir yoldur.

8
Antimony

final etiketli bir fonksiyon için virtual olmalıdır, yani C++ 11 §10.3 paragrafında. 2:

[...] Kolaylık sağlamak için her sanal işlevin kendisini geçersiz kıldığını söylüyoruz.

ve para 4:

Bazı B sınıfındaki sanal bir fonksiyon f ise virt-specier final ile ve D. B bir fonksiyon D: f B :: f'yi geçersiz kılar, program hatalı biçimlendirilir. [...]

yani, final, yalnızca sanal işlevlerle (veya mirası engellemek için sınıflarla) kullanılmalıdır. Bu nedenle, örnek virtual dosyasının geçerli C++ kodu olması için kullanılmasını gerektirir.

EDIT: Tamamen açık olmak gerekirse: "nokta", sanal kullanımın neden kullanıldığına dair endişeler sordu. Kullanılmasının en önemli nedeni (i), kod başka türlü derlenmeyeceği için ve (ii) bir kişi yeterli olduğunda daha fazla sınıf kullanarak örneği neden daha karmaşık hale getiriyor? Bu nedenle sanal final işlevli tam bir sınıf örnek olarak kullanılmıştır.

7
Paul Preney

Sanal bir işlev sunma ve onu hemen nihai olarak işaretleme noktasını anlamıyorum.

Bu örneğin amacı final'ın nasıl çalıştığını göstermektir ve tam da bunu yapar.

A pratik amaç bir kararsızın sınıfın boyutunu nasıl etkilediğini görmek olabilir.

struct Base2 {
    virtual void f() final;
};
struct Base1 {
};

assert(sizeof(Base2) != sizeof(Base1)); //probably

Base2, platform özelliklerini test etmek için kullanılabilir ve f() işlevini geçersiz kılmanın bir anlamı yoktur, çünkü yalnızca test amaçlıdır, bu nedenle final olarak işaretlenir. Tabii ki, bunu yapıyorsanız, tasarımda yanlış bir şey var. Şahsen, virtual öğesinin boyutunu kontrol etmek için vfptr işlevine sahip bir sınıf oluşturmazdım.

4
Luchian Grigore

Yukarıdaki Nice cevaplarına ekleme - İşte finalin bilinen bir uygulamasıdır (Java'dan esinlenerek). Bir Base sınıfında wait () işlevini tanımladığımızı ve tüm torunlarında sadece bir wait () uygulaması istediğimizi varsayalım. Bu durumda wait () 'ı final olarak ilan edebiliriz. 

Örneğin: 

class Base { 
   public: 
       virtual void wait() final { cout << "I m inside Base::wait()" << endl; }
       void wait_non_final() { cout << "I m inside Base::wait_non_final()" << endl; }
}; 

ve burada türetilmiş sınıfın tanımı: 

class Derived : public Base {
      public: 
        // assume programmer had no idea there is a function Base::wait() 

        // error: wait is final
        void wait() { cout << "I am inside Derived::wait() \n"; } 
        // that's ok    
        void wait_non_final() { cout << "I am inside Derived::wait_non_final(); }

} 

Wait () bir saf sanal işlevse, yararsız olurdu (ve doğru değil). Bu durumda: derleyici türetilmiş sınıf içinde wait () tanımlamanızı ister. Bunu yaparsanız, bir hata verecektir çünkü wait () nihaidir. 

Son bir fonksiyon neden sanal olmalı? (ki bu da kafa karıştırıcıdır) Çünkü (imo) 1) final kavramı sanal fonksiyonlar kavramına çok yakındır [sanal fonksiyonların birçok uygulaması vardır - final fonksiyonların yalnızca bir uygulaması vardır], 2) uygulaması kolaydır vtables kullanarak nihai etki. 

2
AJed

Eski kodu yeniden aktive ederken (örneğin, bir ana sınıftan sanal olan bir yöntemi kaldırma), bu, alt sınıfların hiçbirinin bu sanal işlevi kullanmadığından emin olmak için yararlıdır.

// Removing foo method is not impacting any child class => this compiles
struct NoImpact { virtual void foo() final {} };
struct OK : NoImpact {};

// Removing foo method is impacting a child class => NOK class does not compile
struct ImpactChildClass { virtual void foo() final {} };
struct NOK : ImpactChildClass { void foo() {} };

int main() {}
2
Richard Dally

İşte bu nedenle, temel bir sınıfta hem virtual hem de final işlevlerini bildirmeyi seçebilirsiniz:

class A {
    void f();
};

class B : public A {
    void f(); // Compiles fine!
};

class C {
    virtual void f() final;
};

class D : public C {
    void f(); // Generates error.
};

final ile işaretlenmiş bir işlev de olacak ayrıca virtual. Bir işlevi final işaretlemek, türetilmiş bir sınıfta aynı ad ve imzalı bir işlev bildirmenizi önler.

1
Omnifarious

Sanal işlevin son olarak bildirilmesinin faydalı olduğu başka bir durum buldum. Bu durum SonarQube uyarı listesinin bir parçasıdır . Uyarı açıklaması şöyle diyor:

Bir yapıcıdan veya yıkıcıdan geçersiz kılınabilen bir üye işlevini çağırmak, üye işlevini geçersiz kılan bir alt sınıfı başlatırken beklenmeyen davranışlara neden olabilir.

Örneğin:
- Sözleşmeli olarak, alt sınıf sınıfı yapıcısı, üst sınıf yapıcısını çağırarak başlar.
- Üst sınıf yapıcısı üst sınıf işlevini çağırır, çocuk sınıfının geliştiricisi için kafa karıştırıcı olan çocuk sınıfında geçersiz kılınan değil.
- Üye işlevi üst sınıfta tamamen sanalsa, tanımsız bir davranış üretebilir.

Uyumsuz Kod Örneği

class Parent {
  public:
    Parent() {
      method1();
      method2(); // Noncompliant; confusing because Parent::method2() will always been called even if the method is overridden
    }
    virtual ~Parent() {
      method3(); // Noncompliant; undefined behavior (ex: throws a "pure virtual method called" exception)
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0; // pure virtual
};

class Child : public Parent {
  public:
    Child() { // leads to a call to Parent::method2(), not Child::method2()
    }
    virtual ~Child() {
      method3(); // Noncompliant; Child::method3() will always be called even if a child class overrides method3
    }
  protected:
    void method2() override { /*...*/ }
    void method3() override { /*...*/ }
};

Uyumlu Çözüm

class Parent {
  public:
    Parent() {
      method1();
      Parent::method2(); // acceptable but poor design
    }
    virtual ~Parent() {
      // call to pure virtual function removed
    }
  protected:
    void         method1() { /*...*/ }
    virtual void method2() { /*...*/ }
    virtual void method3() = 0;
};

class Child : public Parent {
  public:
    Child() {
    }
    virtual ~Child() {
      method3(); // method3() is now final so this is okay
    }
  protected:
    void method2() override { /*...*/ }
    void method3() final    { /*...*/ } // this virtual function is "final"
};
0
dismine

Bunun yerine:

public:
    virtual void f();

Bunu yazmak için yararlı buluyorum:

public:
    virtual void f() final
        {
        do_f(); // breakpoint here
        }
protected:
    virtual void do_f();

Asıl sebep, şimdi potansiyel olarak birçok geçersiz kılınan uygulamalardan herhangi birine göndermeden önce kesme noktası için tek bir yerinizin olması. Ne yazık ki (IMHO), "son" demek aynı zamanda "sanal" demenizi de gerektirir.

0
Kevin Hopps

virtual + final, örneği kısa yapmak için bir işlev bildiriminde kullanılır.

virtual ve final öğelerinin sözdizimi ile ilgili olarak, Vikipedi örneği, virtual void f(); içeren Base1 ve void f() final; içeren Base2 (aşağıya bakınız) ile struct Base2 : Base1 eklenerek daha anlamlı olacaktır.

Standart

N3690 ’ye:

  • virtual __function-specifier __ __ 'ın decl-specifier-seq parçası olabilir.
  • finalvirt-specifier-seq'nin bir parçası olabilir

Anahtar sözcükvirtual ve Tanımlayıcıları özel anlamı olanfinal birlikte kullanmak zorunda değilsiniz. Sn 8.4, fonksiyon tanımları (tercih opt = isteğe bağlı):

fonksiyon tanımlama:

öznitelik belirticisi-seq (opt) decl-belirtici-seq (opt) bildirgesi virt-specier-seq (opt) işlev gövdesi

Uygulama

C++ 11 ile virtual öğesini kullanırken final anahtar sözcüğünü atlayabilirsiniz. Bu, gcc> 4.7.1'de, c ++> 3.0'da C++ 11 ile msvc'de ... derlenir (bkz. compiler Explorer ).

struct A
{
    virtual void f() {}
};

struct B : A
{
    void f() final {}
};

int main()
{
    auto b = B();
    b.f();
}

Not: cppreference örneği ayrıca aynı beyanda finalle birlikte sanal kullanmıyor.

PPS: Aynısı override için de geçerlidir.

0
Roi Danton