2015-09-18 15 views
18

L'attributo [[noreturn]] può essere applicato a funzioni che non sono destinate a restituire. Per esempio:Sovrascrittura di una funzione virtuale [[Noreturn]]

[[noreturn]] void will_throw() { throw std::runtime_error("bad, bad, bad ...."); } 

Ma ho incontrato la seguente situazione (no, non ho progettare questo):

class B { 
public: 
    virtual void f() { throw std::runtime_error(""); } 
}; 

class D : public B { 
    void f() override { std::cout << "Hi" << std::endl; } 
}; 

mi piacerebbe davvero per posizionare l'attributo [[noreturn]] nella dichiarazione B::f(). Ma non sono chiaro su cosa succede all'override nella classe derivata. Il ritorno con successo da una funzione [[noreturn]] ha come risultato un comportamento indefinito, e certamente non voglio che se una sovrascrittura erediti anche l'attributo.

La domanda: Con l'override [[noreturn] virtual void B::f(), faccio io eredito l'attributo [[noreturn]]?

Ho esaminato lo standard C++ 14 e ho difficoltà a determinare se gli attributi sono ereditati.

+1

Non lo so, sto solo supponendo ma al punto in cui si chiama B-> f() il compilatore molto probabilmente non avrà idea di quale sia il tipo dinamico di B, quindi penso che il compilatore ottimizzerà con noreturn. Non so perché questo disegno, se è perché B non ha un'implementazione per questo e questo lancio è un segno di "non chiamare questo, solo nelle classi derivate" quindi firmarlo con noreturn non ha senso per me. (beh, anche il design non ha molto senso.: D).Se si utilizza il tipo statico di D, mi aspetto che il compilatore non ottimizzi il noreturn. Ancora: lo indovino. – Melkon

+0

's/non significava restituire un valore/non intendeva restituire affatto /' –

+0

@LightnessRacesinOrbit Grazie. Origitato modificato inviare. – KyleKnoepfel

risposta

1

In pratica, né g++, clangMSVC considerano l'attributo [[noreturn]] come ereditato

#include <iostream> 

struct B { 
public: 
    [[noreturn]] virtual void f() { std::cout << "B\n"; throw 0; } 
}; 

struct D : public B { 
    void f() override { std::cout << "D\n"; } 
}; 

int main() 
{ 
    try { B{}.f(); } catch(...) {} 
    D{}.f(); 

    B* d = new D{}; 
    d->f(); 
} 

che stampa "B", "D" e "D" per tutti e tre i compilatori.

+0

Dal momento che il ritorno da una funzione 'noreturn' è UB, qual è il comportamento previsto? – dyp

+1

@dyp che ritorna da 'BB {l.f()' dà un avvertimento su clang, quindi mi aspettavo un avvertimento per 'd.f()' se noreturn era transitivo (evern se UB è ndr) – TemplateRex

8

Ho attraversato lo standard e non c'è alcuna indicazione che sia lo [[noreturn]] nello specifico, sia gli attributi più in generale, siano "ereditati" dalle funzioni di override.

E 'difficile fornire una prova negativa, e lo standard di fatto non dichiara questo in entrambi i casi, ma, dal momento che A::f() e B::f() sono ancora funzioni distinte e l'unico comportamento descritto è definito in termini di funzioni, penso che tu sia al sicuro per contrassegnare A::f() come [[noreturn]].

Detto questo, non posso immaginare quale utile ottimizzazione il compilatore potrebbe successivamente eseguire, data la spedizione dinamica.

+2

** + 1 **: gli attributi non sono ereditati poiché si applicano ([testo standard] * "appertains" *) all'entità in cui sono scritti. Nell'esempio OP '[[noreturn]]' si applica a 'void B :: f()' - 'void D :: f()' è un'entità completamente diversa. –

3

considerare ciò che in realtà si sta dicendo:

class B 
{ 
public: 
    [[noreturn]] virtual void f() { throw std::runtime_error(""); } 
}; 

Certamente il lettore umano, e, eventualmente, il compilatore, potrebbe interpretare questo come un 'contratto', vale a dire
"f() ritorno solito, te lo prometto"

Questo dovrebbe quindi essere applicato anche alle sostituzioni di f() oppure si sta violando il contratto.

Lo standard potrebbe essere sottodescritto su questo, ma anche se funziona, lo raccomanderei in base alla leggibilità.

+0

Non è ovvio per me che '[[noreturn]' specifica un contratto più di quanto lo specificatore di accesso della classe base specifichi un contratto. Vale a dire, l'accesso pubblico/protetto/privato di una funzione sovrascritta in una classe derivata è indipendente da quello nella classe base. Il copertoncino per me, tuttavia, è legato alla tua ultima affermazione. Poiché lo standard non lo specifica, ricade in un comportamento non specificato e pertanto non dovrei dipendere da esso. – KyleKnoepfel

+1

@KyleKnoepfel Il contratto è tra il chiamante e il chiamato, non tra la base e il derivato. Lo specificatore di accesso può essere considerato parte di questo contratto. 'public: void f()' è promettente che 'b-> f()' sarà sempre accessibile, e quella promessa sarà valida anche se 'f' è sovrascritto da una funzione privata in' D'. Un argomento migliore sarebbe che '[[noreturn]]' non è più una parte del contratto di quanto lo sia '= 0' di una pura funzione virtuale. – Oktalist