Pointer-a-membro-func, mascherina & eredità disguido
-
29-09-2019 - |
Domanda
Sto cercando di creare un oggetto generico "callback" che conterrà dati arbitrari e richiamare le funzioni di membro di classi correlate. A causa della politica interna, non posso usare Boost.
L'aspetto oggetto callback come questo:
template<typename Object, typename Data>
class Callback
{
public:
typedef void (Object::*PHandler)(Callback*);
Callback(Object* obj, PHandler handler) : pObj(obj), pHandler(handler) {}
Callback& set(PHandler handler) { pHandler = handler; return *this; }
void run() { (pObj->*pHandler)(this); }
public:
Data data;
protected:
Object* pObj;
PHandler pHandler;
};
E la classe funziona su:
struct Object1
{
struct Data { int i; };
typedef Callback<Object1, Data> Callback1;
void callback(Callback1* pDisp) { printf("%cb\n", pDisp->data.i); }
void test()
{
Callback1 cb(this, &Object1::callback);
cb.data.i = 1;
cb.run();
}
};
Il seguente test funziona come previsto:
Object1 obj1;
obj1.test();
Fin qui tutto bene.
Tuttavia, quando un collega ha cercato di derivare dalla classe Callback
invece di utilizzare un typedef
, hanno ottenuto gli errori di compilazione a causa di puntatori incompatibili:
struct Object2
{
struct Data { int i; Data(int j) { i = j; } };
class Callback2 : public Callback<Object2, Data>
{
Callback2(Object2* obj, PHandler handler, int i) : Callback(obj, handler) { data.i = i; }
};
void callback(Callback2* pDisp) { printf("%cb\n", pDisp->data.i); }
void test()
{
Callback2 cb(this, &Object2::callback, 2);
cb.run();
}
};
Ho provato ad utilizzare il "curiosamente ricorrenti modello modello" nella classe richiamata ed è riuscito a ottenere classi lavoratrici derivata, ma si è rotto il codice che ha utilizzato il metodo typedef
.
La mia domanda è:
Come faccio a modificare il Callback
classe per il lavoro con entrambi i casi, e senza richiedere lavoro supplementare da parte degli utenti della classe?
Soluzione
Devi passare il tipo di classe della derivata. Per non rompere la typedef-modo, si può può dare quel parametro un valore predefinito. Qualcosa come il seguente dovrebbe funzionare
template<typename Object, typename Data, typename Derived = void>
class Callback;
namespace detail {
template<typename Object, typename Data, typename Derived>
struct derived {
typedef Derived derived_type;
};
template<typename Object, typename Data>
struct derived<Object, Data, void> {
typedef Callback<Object, Data, void> derived_type;
};
}
template<typename Object, typename Data, typename Derived>
class Callback : detail::derived<Object, Data, Derived>
{
typedef typename
detail::derived<Object, Data, Derived>::derived_type
derived_type;
derived_type &getDerived() {
return static_cast<derived_type&>(*this);
}
public:
// ... stays unchanged ...
derived_type& set(PHandler handler) {
pHandler = handler; return getDerived();
}
void run() { (pObj->*pHandler)(&getDerived()); }
// ... stays unchanged ...
};
In alternativa si può semplicemente avere due classi per questo. Uno per eredità e uno se non si eredita. Il primo è per eredità
template<typename Object, typename Data, typename Derived>
class CallbackBase
{
typedef Derived derived_type;
derived_type &getDerived() {
return static_cast<derived_type&>(*this);
}
public:
// ... stays unchanged ...
derived_type& set(PHandler handler) {
pHandler = handler; return getDerived();
}
void run() { (pObj->*pHandler)(&getDerived()); }
// ... stays unchanged ...
};
e la seconda è per i non-eredità. È possibile utilizzare la classe base per questo
template<typename Object, typename Data>
struct Callback : CallbackBase<Object, Data, Callback<Object, Data> > {
Callback(Object* obj, PHandler handler) : Callback::CallbackBase(obj, handler) {}
};
Altri suggerimenti
E 'un peccato che si lavora per una società che sostiene reinventare le ruote e l'utilizzo del bronzo C ++. Tuttavia, date le circostanze e il codice che hai postato, c'è una soluzione abbastanza semplice senza fare troppi cambiamenti. Ho dovuto fare cose simili davanti non a causa della politica della compagnia, ma perché stavamo sviluppando un kit di sviluppo software e non volevamo per richiedere che gli utenti del SDK avere una versione specifica di spinta installata.
A proposito, credo che quello che siete veramente dopo è un meccanismo di segnali e slot:. Suggerisco di studiare, se si vuole fare questo progetto un po 'più conforme alle soluzioni consolidate
ho preso un paio di ipotesi liberale: quella principale è che i dati non devono essere memorizzati nel callback e può invece essere passato dal mittente. Questo dovrebbe essere un design più flessibile. E 'ancora molto semplice - si può prendere questo molto più lontano (es: variabile numero di argomenti e tipi di ritorno personalizzati, una classe di segnale, le notifiche differite, ecc).
template<typename Data>
class CallbackInterface
{
public:
virtual ~CallbackInterface() {}
virtual void run(const Data& data) = 0;
virtual CallbackInterface* clone() const = 0;
};
template<typename Object, typename Data>
class CallbackMethod: public CallbackInterface<Data>
{
public:
typedef void (Object::*PHandler)(const Data&);
CallbackMethod(Object* obj, PHandler handler) : pObj(obj), pHandler(handler) {}
virtual void run(const Data& data) { (pObj->*pHandler)(data); }
virtual CallbackInterface* clone() const {return new CallbackMethod(*this); }
protected:
Object* pObj;
PHandler pHandler;
};
template <class Data>
class Callback
{
public:
template <class Object>
Callback(Object* obj, void (Object::*method)(const Data&) ):
cb(new CallbackMethod<Object, Data>(obj, method))
{
}
Callback(const Callback& other): cb(other.cb->clone() )
{
}
Callback& operator=(const Callback& other)
{
delete cb;
cb = other.cb->clone();
return *this;
}
~Callback()
{
delete cb;
}
void operator()(const Data& data) const
{
cb->run(data);
}
private:
CallbackInterface<Data>* cb;
};
Esempio di utilizzo:
struct Foo
{
void f(const int& x)
{
cout << "Foo: " << x << endl;
}
};
struct Bar
{
void f(const int& x)
{
cout << "Bar: " << x << endl;
}
};
int main()
{
Foo f;
Callback<int> cb(&f, &Foo::f);
cb(123); // outputs Foo: 123
Bar b;
cb = Callback<int>(&b, &Bar::f);
cb(456); // outputs Bar: 456
}
Come si può vedere, l'oggetto callback per sé non richiede il tipo di oggetto da passare in un argomento di un template, consentendo in tal modo a punto metodi di qualsiasi tipo, a condizione che il metodo conforme alla firma: some_class vuoto :: some_method (const dati &). Memorizzare un elenco di questi oggetti richiamata in una classe in grado di chiamare tutti loro e avete voi stessi un segnale con slot collegati.
A causa della politica interna, non posso usare Boost.
Esci;)
Tuttavia, quando un collega ha cercato di derivare dalla classe richiamata invece di utilizzare un typedef
Fucilarli.
mi sento per te, davvero.