In C ++, quando dovrei usare la finale nella dichiarazione del metodo virtuale?

10

So che la parola chiave final viene utilizzata per impedire che il metodo virtuale venga sovrascritto dalle classi derivate. Tuttavia, non riesco a trovare alcun esempio utile quando dovrei davvero utilizzare la parola chiave final con il metodo virtual . Inoltre, sembra che l'utilizzo di final con i metodi virtuali sia un cattivo odore poiché non consente ai programmatori di estendere la classe in futuro.

La mia domanda è successiva:

C'è qualche caso utile quando dovrei davvero usare final in virtual dichiarazione del metodo?

    
posta metamaker 07.07.2015 - 20:11
fonte

3 risposte

11

Un riepilogo di ciò che fa la parola chiave final : Diciamo che abbiamo la classe base A e la classe derivata B . La funzione f() può essere dichiarata in A come virtual , il che significa che la classe B può sovrascriverla. Ma allora la classe B potrebbe desiderare che qualsiasi classe che è ulteriormente derivata da B non dovrebbe essere in grado di sovrascrivere f() . Questo è il momento in cui dobbiamo dichiarare f() come final in B . Senza la parola chiave final , una volta definito un metodo come virtuale, qualsiasi classe derivata sarebbe libera di sovrascriverla. La parola chiave final viene utilizzata per porre fine a questa libertà.

Un esempio del perché ti serve una cosa del genere: supponiamo che la classe A definisca una funzione virtuale prepareEnvelope() , e la classe B la sovrascriva e la implementa come una sequenza di chiamate a i suoi metodi virtuali stuffEnvelope() , lickEnvelope() e sealEnvelope() . La classe B intende consentire alle classi derivate di sovrascrivere questi metodi virtuali per fornire le proprie implementazioni, ma la classe B non vuole consentire ad alcuna classe derivata di sovrascrivere prepareEnvelope() e quindi modificare l'ordine delle cose, leccare, sigillare, o ometto invocando uno di loro. Quindi, in questo caso la classe B dichiara prepareEnvelope() come finale.

    
risposta data 07.07.2015 - 20:34
fonte
2

Spesso è utile dal punto di vista del design essere in grado di contrassegnare le cose come immutabili. Allo stesso modo const fornisce guard del compilatore e indica che uno stato non dovrebbe cambiare, final può essere usato per indicare che il comportamento non dovrebbe cambiare ulteriormente nella gerarchia dell'ereditarietà.

Esempio

Considera un videogioco in cui i veicoli portano il giocatore da una posizione all'altra. Tutti i veicoli devono verificare che stiano viaggiando verso una posizione valida prima della partenza (assicurandosi che la base nel luogo non venga distrutta, ad es.). Possiamo iniziare a utilizzare l'idioma di interfaccia non virtuale (NVI) per garantire che questo controllo venga effettuato indipendentemente dal veicolo.

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

Ora diciamo che abbiamo veicoli volanti nel nostro gioco, e qualcosa che i tutti veicoli volanti richiedono e hanno in comune è che devono passare attraverso un controllo di sicurezza all'interno dell'hangar prima del decollo .

Qui possiamo utilizzare final per garantire che tutti i veicoli volanti effettuino tale ispezione e comunichino anche questo requisito di progettazione dei veicoli volanti.

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

Usando final in questo modo, stiamo efficacemente estendendo la flessibilità dell'interfaccia idioma non virtuale per fornire un comportamento uniforme verso il basso della gerarchia dell'ereditarietà (anche come ripensamento, contrastando il fragile problema della classe base) a virtuale funziona da solo Inoltre, ci compriamo una sala di manovra per apportare modifiche centrali che influiscono su tutti i tipi di veicoli volanti come un ripensamento senza modificare ogni singola implementazione di veicolo volante esistente.

Questo è uno di questi esempi di utilizzo di final . Ci sono contesti che incontrerai in cui semplicemente non ha senso che una funzione membro virtuale venga sovrascritta più avanti - farlo potrebbe portare a un design fragile e una violazione dei tuoi requisiti di progettazione.

Ecco dove final è utile dal punto di vista del progetto / architettonico.

È anche utile dal punto di vista di un ottimizzatore poiché fornisce all'ottimizzatore queste informazioni di progettazione che gli consentono di virtualizzare le chiamate di funzioni virtuali (eliminando il sovraccarico di dispacciamento dinamico e spesso in modo più significativo, eliminando una barriera di ottimizzazione tra il chiamante e il chiamante). p>

Domanda

Dai commenti:

Why would final and virtual ever be used at the same time?

Non ha senso che una classe base nella radice di una gerarchia dichiari una funzione come virtual e final . Mi sembra abbastanza sciocco, perché farebbe in modo che sia il compilatore che il lettore umano debbano saltare attraverso cerchi inutili che possono essere evitati semplicemente evitando virtual a titolo definitivo in questo caso. Tuttavia, le sottoclassi ereditano le funzioni dei membri virtuali in questo modo:

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

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

In questo caso, indipendentemente dal fatto che Bar::f utilizzi esplicitamente la parola chiave virtuale, Bar::f è una funzione virtuale. La parola chiave virtual diventa quindi opzionale in questo caso. Quindi potrebbe avere senso che Bar::f sia specificato come final , anche se è una funzione virtuale ( final può solo essere usato per le funzioni virtuali).

E alcune persone potrebbero preferire, stilisticamente, di indicare esplicitamente che Bar::f è virtuale, in questo modo:

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

Per me è un po 'ridondante usare entrambi gli specificatori virtual e final per la stessa funzione in questo contesto (allo stesso modo virtual e override ), ma in questo caso è una questione di stile. Alcune persone potrebbero scoprire che virtual comunica qui qualcosa di prezioso, proprio come usare extern per le dichiarazioni di funzioni con linkage esterno (anche se è facoltativo privo di altri qualificatori di linkage).

    
risposta data 01.01.2016 - 07:20
fonte
0
  1. Abilita molte ottimizzazioni, perché potrebbe essere noto al momento della compilazione quale funzione viene chiamata.

  2. Fai attenzione a lanciare la parola "codice odore". "finale" non rende impossibile estendere la classe. Fai doppio clic sulla parola "finale", premi backspace ed estendi la classe. TUTTAVIA finale è una documentazione eccellente che lo sviluppatore non si aspetta che tu abbia la precedenza sulla funzione, e che a causa di ciò il prossimo sviluppatore dovrebbe stare molto attento, perché la classe potrebbe smettere di funzionare correttamente se il metodo finale viene sovrascritto in quel modo.

risposta data 07.07.2015 - 20:20
fonte

Leggi altre domande sui tag