2014-07-07 21 views
13

Ho una funzione di classe base virtuale che non dovrebbe mai essere utilizzata in una particolare classe derivata. C'è un modo per "cancellarlo"? Naturalmente posso solo dargli una definizione vuota, ma preferirei fare il suo tentativo di usare un errore in fase di compilazione. Il C++ 11 delete identificatore sembra quello che vorrei, maElimina funzione virtuale da una classe derivata

class B 
{ 
    virtual void f(); 
}; 

class D : public B 
{ 
    virtual void f() = delete; //Error 
}; 

non verrà compilato; gcc, almeno, non mi permetterà di cancellare una funzione che ha una versione base non eliminata. C'è un altro modo per ottenere la stessa funzionalità?

+0

Com'è possibile? Vuoi l'errore del compilatore in 'D * d = new D(); static_cast (d) -> f() '? –

+1

@BryanChen Se viene lanciato per puntare alla classe base, dovrebbe agire come un puntatore alla classe base, nel qual caso 'D :: f' verrebbe comunque chiamato, quindi sì. –

+0

E quale comportamento ti aspetteresti allora? – Theolodis

risposta

22

Non è consentito dallo standard, tuttavia è possibile utilizzare uno dei seguenti due metodi per ottenere un comportamento simile.

La prima sarebbe quella di utilizzare using per modifica la visibilità del metodo a privati ​​, impedendo così ad altri di utilizzarlo. Il problema con questa soluzione è che chiamare il metodo su un puntatore della super-classe non genera un errore di compilazione.

class B 
{ 
public: 
    virtual void f(); 
}; 

class D : public B 
{ 
private: 
    using B::f; 
}; 

La soluzione migliore che ho trovato finora per ottenere un errore di compilazione quando si chiama il metodo D s è quello di utilizzare un static_assert con una struttura generica che eredita da false_type. Fintanto che nessuno chiama mai il metodo, la struttura rimane non controllata e il static_assert non fallirà.

Se il metodo è chiamato, tuttavia, la struttura è definita e il suo valore è falso, pertanto static_assert ha esito negativo.

Se il metodo non viene chiamato, ma si tenta di chiamare su un puntatore della classe super, poi D s metodo non è definito e si ottiene un errore di compilazione undefined reference.

template <typename T> 
struct fail : std::false_type 
{ 
}; 

class B 
{ 
public: 
    virtual void f() 
    { 
    } 
}; 

class D : public B 
{ 
public: 
    template<typename T = bool> 
    void 
    f() 
    { 
     static_assert (fail<T>::value, "Do not use!"); 
    } 
}; 

Un'altra soluzione potrebbe essere quella di un'eccezione quando il metodo è usato, ma sarebbe solo un tiro su runtime.

+1

Bello, ma 'B * d = nuovo D(); d-> f(); 'funzionerà senza errori (e' B: f' verrà chiamato), ma questo è il tipo di situazione in cui vorrei che si verificasse un errore. –

+0

@MattPhillips come hai affermato nei tuoi commenti che il comportamento sarebbe stato previsto in ogni caso, o sbaglio? – Theolodis

+0

No, non vorrei mai chiamare la versione della classe base. La mia risposta a Bryan Chen è stata aggiornata dopo aver risposto, vorrei un errore del compilatore in questo caso. –

10

La norma non consente di eliminare qualsiasi membro di una classe base in una classe derivata per una buona ragione:
questo modo rompe l'eredità, in particolare il "è-un" rapporto.

per motivi legati, ma non permette una classe derivata per definire una funzione eliminato nella classe base:
Il gancio non è un qualsiasi fa più parte del contratto della classe base, e quindi ti impedisce di fare affidamento sulle garanzie precedenti che non sono più valide.

Se si vuole ottenere difficile, è possibile forzare un errore, ma dovrà essere link-tempo, invece di tempo di compilazione:
dichiarare la funzione di membro, ma non bisogna mai lo definiscono (Questo non è al 100 % garantito per funzionare per le funzioni virtuali però).
Meglio dare un'occhiata all'attributo deprecato GCC per gli avvisi precedenti __attribute__ ((deprecated)).
Per dettagli e simili MS magic: C++ mark as deprecated

+0

OK +1 per il suggerimento sull'errore di collegamento. Non mi interessa usare 'delete' in modo specifico, voglio solo la funzionalità. –

+0

In realtà, ciò produce un errore "vtable non definito" anche se "f" non viene mai chiamato, come mostrato [qui] (http://ideone.com/JW0uKU). –

+0

@MattPhillips: alcuni compilatori/linker non si lamentano se l'implementazione di qualche metodo virtuale non è presente. A volte dipende dalle opzioni del compilatore. Tuttavia, la migliore scommessa è sempre la deprecazione. – Deduplicator

0

Quello che puoi fare è semplicemente lanciare un'eccezione nell'implementazione derivata.Ad esempio, il framework Java Collections lo fa in modo abbastanza eccessivo: quando un'operazione di aggiornamento viene eseguita su una raccolta che è immutabile, il metodo corrispondente getta semplicemente un UnsupportedOperationException. Puoi fare lo stesso in C++.

Ovviamente, questo mostrerà un uso dannoso della funzione solo in fase di esecuzione; non al momento della compilazione. Tuttavia, con i metodi virtuali, non è possibile rilevare tali errori al momento della compilazione in ogni caso a causa del polimorfismo. Es .:

B* b = new D(); 
b.f(); 

Qui, si memorizza una D in una variabile B*. Quindi, anche se c'era un modo per dire al compilatore che non sei autorizzato a chiamare f su un D, il compilatore non sarebbe in grado di segnalare questo errore qui, perché vede solo B.

1

"Ho una funzione di classe base virtuale che non dovrebbe mai essere utilizzata in una particolare classe derivata."

Per alcuni aspetti questa è una contraddizione. L'intero scopo delle funzioni virtuali è fornire diverse implementazioni del contratto fornito dalla classe base. Quello che stai cercando di fare è rompere il contratto . Il linguaggio C++ è progettato per impedirti di farlo. Questo è il motivo per cui ti costringe ad implementare pure funzioni virtuali quando istanzia un oggetto. Ed è per questo che non ti permetterà di cancellare parte del contratto .

Che cosa sta succedendo è una cosa buona . Probabilmente ti impedisce di implementare una scelta di design inappropriata.

Tuttavia:

A volte può essere opportuno avere un'implementazione vuota che non fa nulla:

void MyClass::my_virtual_function() 
{ 
    // nothing here 
} 

O un'implementazione vuoto che restituisce un "fallito" status:

bool MyClass::my_virtual_function() 
{ 
    return false; 
} 

Tutto dipende da cosa stai cercando di fare. Forse se potessi dare più informazioni su ciò che stai cercando di ottenere, qualcuno potrebbe indicarti la giusta direzione.

EDIT

Se ci pensate, per evitare di chiamare la funzione per un tipo specifico derivato, il chiamante avrebbe bisogno di sapere che tipo si sta chiamando. L'intero punto di chiamare un riferimento/puntatore di classe base è che non si sa quale tipo derivato riceverà la chiamata.

+0

"L'intero punto di chiamare una classe base ..." Questo è giusto per il caso generale, ma non sempre; se fosse sempre vero, il linguaggio C++ non conterrebbe 'static_cast' e' dynamic_cast'. A volte l'identità di classe derivata è importante per il codice chiamante e questa una di quelle volte. –

+0

A volte è importante trattare direttamente con la classe derivata. Questo è davvero il motivo per cui dynamic_cast è usato. Ma allora non stai più chiamando il riferimento/puntatore della classe base, quindi la mia affermazione non si applica realmente. Ma speravo che avrebbe risposto alla tua domanda perché non è permesso. – Galik