View Full Version : [C++] Ricordatemi una keyword esotica!
Salve,
ho fatto un magheggio di questo tipo:
class Kernel :
public Task::ListenerImpl<ValueQuery>,
public Task::ListenerImpl<SomeTask>,
public Task::ListenerImpl<SomeOther>
Dove ListenerImpl<T> eredita Listener.
Ora, tutto funziona bene, e posso fare dei metodi callback distinti per ogni tipo;
però: se uso this come Listener*, il cast è giustamente ambiguo perchè Listener è ereditato ben 3 volte! :muro:
Mi ricordo che c'era una keyword assurda ed esotica che faceva ereditare le superclassi una ed una sola volta... come Listener in questo caso.
La sapete? O almeno ditemi cosa potrei cercare su Google :asd:
Mi dà l'idea che mi ricordavo male :asd:
Vabbè alla fine basta fare il cast ad uno dei 3 tipi...
wizard1993
13-11-2009, 20:10
che io sapessi c'era un "giochino" per evitare l'inclusione ciclica dei file sorgente, ma nulla più
Salve,
ho fatto un magheggio di questo tipo:
class Kernel :
public Task::ListenerImpl<ValueQuery>,
public Task::ListenerImpl<SomeTask>,
public Task::ListenerImpl<SomeOther>
Dove ListenerImpl<T> eredita Listener.
Ora, tutto funziona bene, e posso fare dei metodi callback distinti per ogni tipo;
però: se uso this come Listener*, il cast è giustamente ambiguo perchè Listener è ereditato ben 3 volte! :muro:
Mi ricordo che c'era una keyword assurda ed esotica che faceva ereditare le superclassi una ed una sola volta... come Listener in questo caso.
La sapete? O almeno ditemi cosa potrei cercare su Google :asd:
template <typename T>
class ListenerImpl : public virtual Listener
{
/* */
};
o giu' di li'.
Si era proprio quello grazie mille :D
Però ora che ci ho provato, l'unificare la classe base rende impossibile distinguere i metodi di callbacks in base al tipo... quindi mi sembra sensato che l'utente deve specificare a quale "parte" del listener mandare il messaggio.
Boh...a me una cosa del genere puzza un po'. Non è meglio estrarre un'interfaccia comune alle tre classi (Listener appunto) e poi aggiungere un membro di tutte e tre all'interno di Kernel ? Solo vedendo due righe di codice non riesco proprio a vederci una relazione di ereditarietà...
Beh, la classe Kernel in realtà è solo un test, è una "classe ipotetica" che potrebbe richiedere un callback di completamento di un Task...
ho pensato che in questo modo venisse "stiloso" perchè, dato che per usare l'interfaccia Task c'è bisogno di farne una sottoclasse, usare un elenco di Listener<T> permette di avere un metodo di callback per ogni tipo, con il puntatore passato già del tipo giusto, tipo nell'esempio di prima:
taskCompleted( ValueQuery* );
taskCompleted( SomeTask* );
taskCompleted( SomeOther* );
etc...
Poi certo si può anche ereditare il metodo generico che passa Task* ma poi devi procedere autonomamente ai cast (spesso molto poco "solidi")...
Imho non si usa l'ereditarietà per risparmiare la scrittura di alcuni metodi, si usa quando c'è una relazione fra classe base e derivata.
In questo caso non mi sembra che ci sia, sarebbe come ereditare una classe macedonia dalle classi pera, mela e banana...
Non capisco la critica onestamente :stordita:
La relazione in questo caso è solo che devo offrire all'utente un modo per ricevere un callback quando il Task (che è asincrono!) viene completato... non vedo quale altro modo più semplice ci sia se non permettere all'user di ereditare il Listener che preferisce...
E poi non è che l'ereditarietà serve solo quando A is B eh...
Se ne hai voglia ricapitolami bene la situazione...non sto dicendo che l'unica relazione che si può esprimere con l'ereditarietà sia a is b, sto dicendo che molte volte in cui si fanno questi pastrocchi (una ereditarietà triplada classi non astratte per me lo è) non è necessario.
Vediamo cosa ho capito: ci sono questi Listener che sono delle classi che si aspettano di ricevere un messaggio di completamento da una operazione asincrona.
Vuoi che Kernel abbia la possibilità di stare in ascolto su tre tipi di eventi distinti. Ovviamente mi immagino che dovrai registrare questi listener presso l'operazione asincrona...
Premetto che sto derivando questi discorsi dai nomi delle classi e dalla semplice intestazione che hai proposto, quindi posso benissimo essere smentito.
Tu vuoi registrare direttamente un'istanza di Kernel presso gli eventi asincroni in modo che venga chiamato direttamente un metodo della classe.
Prima di tutto mi immagino che il compito principale della classe Kernel, dato il nome, non sia di ricevere le notifiche, ma di fare molte altre cose, fra cui la ricezione delle notifiche. Derivando una classe se ne caratterizza il comportamento, quindi se il compito principale di Kernel non è ricevere le notifiche per me l'hai caratterizzata male.
Io separerei la classe Kernel dalla ricezione delle notifiche, creerei una classe KernelListener, addirittura una per ogni tipo di notifica o un template, che prende come parametro l'istanza della classe kernel e si occupa di eseguire le operazioni necessarie su Kernel al momento della ricezione dell'evento.
Ad esempio:
template <typename T>
class KernelListener: public ListenerImpl<T>
{
private:
Kernel *kernel;
public:
KernelListener(Kernel *k):kernel(k){};
virtual void taskCompleted(T *info)
{
ListenerImpl<T>::taskCompleted(info);
kernel->notifyTaskCompleted(info);
}
};
class Kernel
{
private:
KernelListener<ClassA> listener1;
KernelListener<ClassB> listener2;
KernelListener<ClassC> listener3;
public:
void registerListener(AsyncEvent1 evt1, AsyncEvent2 evt2, AsyncEvent3 evt3)
{
evt1.register(listener1);
evt2.register(listener2);
evt3.register(listener3);
}
void notifyTaskCompleted(ClassA info);
void notifyTaskCompleted(ClassB info);
void notifyTaskCompleted(ClassC info);
};
Questa sopra per me è la corretta implementazione di ciò che vuoi fare, certo c'è da scrivere qualche linea di codice in più, ma non c'è alcuna forzatura.
Grazie della spiegazione :D
Allora; premetto che ci hai preso sulle funzioni di più o meno tutte le classi dal loro nome :D
Tuttavia Kernel da quando ho iniziato a "smanettare" con i Task è rimasto privo di funzioni proprie se non lanciare il task iniziale, quindi per evitare forzature pensavo proprio di toglierlo.
E poi ListenerImpl<T> in realtà è una classe astratta formata da 2 metodi... in pratica è un'interfaccia generica stile Java, e per questo non mi sembrava brutto più di tanto ereditarne più d'una (caso comunque molto raro in teoria).
class Listener : public BaseObject
{
public:
virtual void taskCompleted( Task* t )=0;
};
template <class T>
class ListenerImpl : public Listener
{
public:
void taskCompleted( Task* t )
{
taskCompleted( static_cast<T*>(t) );
}
virtual void taskCompleted( T* t )=0;
};
Comunque, la faccenda dei listeners è dovuta al fatto che stavo sperimentando un'ipotetica "libreria di threading" giusto per imparare i concetti base; quindi Listener appartiene alla libreria, ma deve essere usato all'esterno e quindi io sto fornendo solamente un'interfaccia.
Poi ovvio che sta cosa non sarà mai usata da nessuno, ma lo scopo è didattico :asd:
La tua idea mi piace, il fatto è che non capisco bene come potrei implementarla; gli eventi sono sostanzialmente *lo stesso* cioè Task Complete; dal Dispatcher di questi famosi task posso ottenere giusto questa informazione, e tratto qualsiasi listener come Listener, il tipo più astratto... non sono a conoscenza del tipo del task che viene completato, dato che il tipo è esteso dall'user.
Quello che cambia è il <T> del Listener passato al Task, tipo
dispatcher->schedule( new TaskQualsiasi( (ListenerImpl<TaskQualsiasi>*)this));
che in realtà tramite i templates viene creato e passato proprio dall'user; dispatcher si limita a chiamare Listener->taskCompleted() che poi viene girato tramite overriding al tipo giusto.
Per cui, data la scarsissima riflessività del C++ non saprei bene come legare una sottoclasse "sconosciuta" di Task ad un dato callback se non domandando all'user di specificare tutte quante le chiamate come nel tuo esempio... cosa che almeno imo, anche se più corretta non vale troppo la candela... tantopiù che il template cast all'assegnazione rimarrebbe in ogni caso.
O altrimenti levo tutto, metto un bel campo void* userData e lascio tutto all'esterno :asd:
Quindi in pratica il problema è passare dei dati al listener mantenendo la genericità dello stesso...
Fatti una classe base astratta dei valori passati a listener !!!
L'utente per passare un dato dovrà implementare un'interfaccia. In questo modo potrai gestire tutto con la classe astratta ;) Inoltre potrai anche avere il listener sotto forma di classe normale e non template.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.