2013-01-16 2 views
11

Prefazione:C++ 11 Elimina sovrascritti Metodo

Questa è una domanda sulle migliori pratiche per quanto riguarda un nuovo significato del operatore delete introdotta con C++ 11 quando applicate a una classe figlia override di un genitore virtuale ereditato metodo.

Background:

Per lo standard, il primo caso d'uso citato è quello di non consentire in modo esplicito le funzioni di chiamata per alcune tipologie in cui le conversioni che altrimenti sarebbe implicita come l'esempio da §8.4.3 delle ultime C++11 standard draft:

struct sometype { 
    sometype() = delete; // OK, but redundant 
    some_type(std::intmax_t) = delete; 
    some_type(double); 
}; 

L'esempio precedente è chiaro e mirato. Tuttavia, il seguente esempio in cui il nuovo operatore è stato sovrascritto e impedito di essere chiamato definendolo come eliminato, mi ha iniziato a pensare ad altri scenari che identificherei successivamente nella sezione domanda (l'esempio di seguito è dal §8.4.3 dello C++11 standard draft):

struct sometype { 
    void *operator new(std::size_t) = delete; 
    void *operator new[](std::size_t) = delete; 
}; 
sometype *p = new sometype; // error, deleted class operator new 
sometype *q = new sometype[3]; // error, deleted class operator new[] 

domanda:

per estensione di questo pensiero per eredità, sono curioso di di altri pensieri per quanto riguarda se il seguente esempio di utilizzo è un caso d'uso chiaro e valido o se si tratta di un abuso chiaro della funzionalità appena aggiunta. Fornire una giustificazione per la risposta (sarà accettato l'esempio che fornisce il ragionamento più convincente). Nell'esempio che segue, il design tenta di mantenere due versioni della libreria (la libreria deve essere istanziata) avendo la seconda versione della libreria ereditata dalla prima. L'idea è di consentire correzioni di bug o modifiche apportate alla prima versione della libreria per propagarsi automaticamente alla seconda versione della libreria, consentendo al contempo alla seconda versione della libreria di concentrarsi solo sulle sue differenze rispetto alla prima versione. Per deprecare una funzione nella seconda versione della libreria, l'operatore delete viene utilizzato per non consentire una chiamata alla funzione di override:

class LibraryVersion1 { 
public: 
    virtual void doSomething1() { /* does something */ } 
    // many other library methods 
    virtual void doSomethingN() { /* does something else */ } 
}; 

class LibraryVersion2 : public LibraryVersion1 { 
public: 
    // Deprecate the doSomething1 method by disallowing it from being called 
    virtual void doSomething1() override = delete; 

    // Add new method definitions 
    virtual void doSomethingElse() { /* does something else */ } 
}; 

Anche se posso vedere molti benefici ad un tale approccio, penso che tendo più verso il pensiero che si tratta di un abuso della funzionalità. La trappola primaria che vedo nell'esempio precedente è che la classica relazione "is-a" dell'eredità è rotta. Ho letto molti articoli che raccomandano fortemente contro qualsiasi uso dell'ereditarietà per esprimere una relazione "sorta di un è un" e utilizzare invece la composizione con le funzioni wrapper per identificare chiaramente le relazioni delle classi. Mentre il seguente esempio, spesso accigliato, richiede uno sforzo maggiore per implementare e mantenere (per quanto riguarda il numero di righe scritte per questo pezzo di codice, poiché ogni funzione ereditata per essere disponibile pubblicamente deve essere esplicitamente richiamata dalla classe ereditaria), l'uso di Delete come illustrato sopra è molto simile in molti modi:

class LibraryVersion1 { 
public: 
    virtual void doSomething1() { /* does something */ } 
    virtual void doSomething2() { /* does something */ } 
    // many other library methods 
    virtual void doSomethingN() { /* does something */ } 
}; 

class LibraryVersion2 : private LibraryVersion1 { 
    // doSomething1 is inherited privately so other classes cannot call it 
public: 
    // Explicitly state which functions have not been deprecated 
    using LibraryVersion1::doSomething2(); 
    // ... using (many other library methods) 
    using LibraryVersion1::doSomethingN(); 

    // Add new method definitions 
    virtual void doSomethingElse() { /* does something else */ } 
}; 

Grazie in anticipo per le vostre risposte e ulteriori indicazioni circa l'uso di questo potenziale caso di cancellazione.

+0

Non penso che 'virtual void doSomething1() override = delete;' sia legale. Cosa vorresti? ((LibraryVersion1 *) (new LibraryVersion2)) -> doSomething1() 'do? –

+0

Per quanto ne so, anche con il cast su LibraryVersion1, la funzione eliminata avrebbe comunque tentato di essere chiamata dato l'override di LibraryVersion2 e il codice non riusciva a compilare. Questo è il punto in cui la relazione "è-a" è interrotta, come indicato nella mia domanda, ma sicuramente imporrebbe la deprecazione come previsto. – statueuphemism

+0

fresh: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf – qPCR4vir

risposta

5

Paragrafo 8.4.3/2 del C++ standard indirettamente vieta l'eliminazione di una funzione che sostituisce una funzione virtuale:

"Un programma che si riferisce a una funzione cancellata in modo implicito o esplicito, oltre a dichiararlo, è mal formato. [Nota: Questo include chiamare la funzione implicitamente o esplicitamente e formare un puntatore o puntatore-a-membro alla funzione"

Richiamando una funzione virtuale sblocco tramite un puntatore alla classe base è un tentativo di implicitamente richiamare la funzione. Pertanto, per 8.4.3/2, un disegno che consente ciò è illegale. Notare inoltre che nessun compilatore conforme al linguaggio C++ 11 ti consente di eliminare una funzione virtuale dominante.

Più esplicitamente, lo stesso ha il mandato dal Paragrafo 10.3/16:.

"Una funzione con una definizione cancellato (8.4) non deve ignorare una funzione che non ha una definizione cancellato Allo stesso modo, una funzione che non ha una definizione eliminato non prevalgono una funzione con una definizione cancellato "

+1

Grazie. In qualche modo ho perso quella gemma. Ho appena trovato un'altra citazione che lo vieta direttamente: "Una funzione con una definizione cancellata (8.4) non sostituisce una funzione che non ha una definizione cancellata Allo stesso modo, una funzione che non ha una definizione cancellata non deve sovrascrivere una funzione con una definizione cancellata . " – statueuphemism

+0

@statueuphemism: giusto, buon punto. lascia che ti integri nella mia risposta –

2

Consideriamo qualche funzione:

void f(LibraryVersion1* p) 
{ 
    p->doSomething1(); 
} 

Questo compilerà prima LibraryVersion2 è ancora scritto.

Così ora si implementa LibraryVersion2 con il virtuale eliminato.

f è già stato compilato.Non sa fino a quando runtime quale LibraryVersion1 sottoclasse è stato chiamato con.

Ecco perché un virtual eliminato non è legale, non ha alcun senso.

migliore che puoi fare è:

class LibraryVersion2 : public LibraryVersion1 
{ 
public: 
    virtual void doSomething1() override 
    { 
     throw DeletedFunctionException(); 
    } 
} 
+0

Eviterei anche questo se avessi la reputazione di farlo. Per quanto riguarda il "meglio che puoi fare", però, lanciare un'eccezione in runtime per segnalare la deprecazione di un metodo mi sembra complicato quando si tratta di deprecare una funzione (di certo non vorrei usare una libreria che lo facesse). Penso che questa situazione potrebbe richiedere una riprogettazione a quel punto.Ma hai ragione, è probabilmente il meglio che il linguaggio potrebbe imporre per una definizione cancellata di un metodo sottoposto a override. – statueuphemism

+0

Non è chiaro quale sia il problema che stai cercando di risolvere. Sembra che tu voglia un modo per gestire il controllo delle versioni delle librerie. Quindi la prima libreria 1 viene scritta, quindi l'applicazione 1 viene scritta utilizzando la libreria 1, quindi la libreria 2 viene scritta e si desidera che l'applicazione 1 continui a funzionare con la libreria 2. –

+0

Il mio esempio è stato un po 'forzato e in realtà non sto cercando di risolvere un problema in questo momento. Stavo postando l'idea come un potenziale metodo per mantenere semplicemente due pezzi di software in cui un pezzo di software eredita direttamente dall'altro senza necessità di ulteriori operazioni di fusione (un po 'come Python 2.7 che ha alcuni punti in comune con Python 3.0+ ma è ancora in manutenzione a causa di differenze significative). Se dovessi mantenere una classe Python3_0 che eredita da Python2_7, le correzioni dei bug nel codice condiviso verrebbero automaticamente propagate. Ci sono altri progetti migliori per realizzare questo però. – statueuphemism

4

10.3p16:.

una funzione con una definizione cancellato (8.4) non deve ignorare una funzione che non ha un dele definizione di ted. Allo stesso modo, una funzione che non ha una definizione cancellata non deve sovrascrivere una funzione con una definizione cancellata.

Le altre risposte spiegano perché piuttosto bene, ma qui c'è l'ufficiale Thou Shalt Not.

+0

Oh, e hai già citato questo in un commento a una risposta diversa. Va bene allora. – aschepler