In OO, uno di solito implementa callback con interfacce: (esempio di massima)Richiamata con interfacce o oggetti funzione?
class Message {}
class IMsgProcessor {
public:
virtual void handle_msg(const Message& msg) = 0;
}
class RequestMsgProcessor : public IMsgProcessor {
virtual void handle_msg(const Message& msg) {
// process request message
}
}
class CustomSocket {
public:
Socket(IMsgProcessor* p) : processor_(p) {}
void receive_message_from_network(const Message& msg) {
// processor_ does implement handle_msg. Otherwise a compile time error.
// So we've got a safe design.
processor_->handle_msg(msg);
}
private:
IMsgProcessor* processor_;
}
Fin qui tutto bene. Con C++ 11, un altro modo per farlo è quello di avere CustomSocket appena ricevuto un'istanza di oggetto std :: function. Non importa dove si è implementato o anche se l'oggetto è un valore non nullo:
class CustomSocket {
public:
Socket(std::function<void(const Message&)>&& f) : func_(std:forward(f)) {}
void receive_message_from_network(const Message& msg) {
// unfortunately we have to do this check for every msg.
// or maybe not ...
if(func_)
func_(msg);
}
private:
std::function<void(const Message&)> func_;
}
Ora qui sono le domande:
1. Che cosa circa gli impatti delle prestazioni? Immagino che una chiamata a una funzione virtuale sia più veloce della chiamata a un oggetto funzione ma quanto più veloce? Sto implementando un sistema di messaggistica veloce e preferirei evitare qualsiasi penalizzazione delle prestazioni non necessaria.
2. In termini di pratiche di ingegneria del software, devo dire che mi piace il secondo approccio migliore. Meno codice, meno file, meno ingombro: nessuna classe di interfaccia. Maggiore flessibilità: è possibile implementare solo un sottoinsieme dell'interfaccia impostando alcuni degli oggetti funzione e lasciando gli altri nulli. Oppure puoi avere diverse parti dell'interfaccia implementate in classi separate o funzioni libere o una combinazione di entrambi (invece che in una singola sottoclasse). Inoltre, CustomSocket può essere utilizzato da qualsiasi classe, non solo sottoclassi di IMsgProcessor. Questo è un vantaggio , a mio parere.
Cosa rispondi? Vedi qualche difetto fondamentale in questi argomenti?
'// Purtroppo dobbiamo fare questo controllo per ogni msg.' - la stessa cosa deve essere vero per il codice di interfaccia. Non si controlla mai se non si ottiene un puntatore nullo ... – Xeo
Informazioni su perfs: la funzione virtuale in pratica aggiunge una ricerca tabella, std :: function inoltra le chiamate a un adattatore specializzato. Se la tua funzione/metodo non è virtuale, potrebbe essere ottimizzato per essere il più veloce. Devo fare un punto di riferimento. Se si sostituisce 'std :: function' con un argomento modello, è possibile renderlo più veloce della chiamata virtuale. – Antoine
@Xeo Non necessariamente: nel codice dell'interfaccia vorrei solo verificare in costruttore che l'oggetto passato non fosse NULL - accettare un NULL non ha senso. Ma nel secondo caso perché è possibile avere un'implementazione parziale dell'interfaccia (ad esempio, solo alcuni oggetti funzione forse non null) è necessario il controllo. – PoP