2013-01-23 12 views
6

che ho scritto la seguente implementazione per un generico sistema di segnale/Slot:perfetta inoltro di argomenti di template variading

template< typename... Args > 
class Signal : NonCopyable 
{ 
public: 

    typedef std::function< void (Args...) > Delegate; 

    void connect(const Delegate& delegate); 
    void operator()(Args&&... args) const; 

private: 

    std::list<Delegate> _delegates; 
}; 

template< typename... Args > 
void Signal<Args...>::connect(const Delegate& delegate) 
{ 
    _delegates.push_front(delegate); 
} 

template< typename... Args > 
void Signal<Args...>::operator()(Args&&... args) const 
{ 
    for (const Delegate& delegate : _delegates) 
     delegate(std::forward<Args>(args)...); 
} 

In seguito, ho provato la mia classe utilizzando i seguenti casi semplici:

Signal<int> signal; 

// Case 1 
signal(0); 

//Case 2 
int i(0); 
signal(i); 

Il caso 1 viene compilato senza problemi. Caso 2, d'altra parte, genera il seguente errore in GCC 4.7.2:

/home/pmjobin/Workspace/main.cpp:1196:10: error: cannot bind ‘int’ lvalue to ‘int&&’ 
/home/pmjobin/Workspace/main.cpp:82:6: error: initializing argument 1 of ‘void Signal<Args>::operator()(Args&& ...) const [with Args = {int}]’ 

capisco il problema ha a che fare con perfetta-forwarding (e la mia incomprensione di quest'ultimo). Tuttavia, mi sono ispirato alle implementazioni std :: make_shared() & std :: make_tuple() e non vedo alcuna differenza nel modo in cui inoltro gli argomenti variadici ai delegati. L'unica differenza degna di nota è che sia make_shared() che make_tuple() sono template di funzioni piuttosto che un modello di classe come l'implementazione Signal sopra.

- EDIT -

In risposta ai vari commenti, qui è una nuova versione della implementazione della classe del segnale, che non soffre dei problemi di cui sopra. Inoltre, è ora possibile disconnettere i delegati usando un token opaco restituito dalla funzione connect. Il risultato potrebbe non essere flessibile e potente come altre implementazioni là fuori (come boost :: signal), ma almeno, ha il vantaggio di essere semplice e leggero.

template< typename Signature > 
class Signal : NonCopyable 
{ 
public: 

    typedef std::function<Signature> Delegate; 

    class DisconnectionToken 
    { 
     DisconnectionToken(typename std::list<Delegate>::iterator it) 
      : _it(it) 
     {} 

     typename std::list<Delegate>::iterator _it; 

     friend class Signal; 
    }; 

    DisconnectionToken connect(const Delegate& delegate); 
    void disconnect(DisconnectionToken& token); 

    template< typename... Args > 
    void operator()(Args&&... args) const; 

private: 

    std::list<Delegate> _delegates; 
}; 

template< typename Signature > 
typename Signal<Signature>::DisconnectionToken Signal<Signature>::connect(const Delegate& delegate) 
{ 
    _delegates.push_front(delegate); 
    return DisconnectionToken(_delegates.begin()); 
} 

template< typename Signature > 
void Signal<Signature>::disconnect(DisconnectionToken& token) 
{ 
    if (token._it != _delegates.end()) 
    { 
     _delegates.erase(token._it); 
     token._it = _delegates.end(); 
    } 
} 

template< typename Signature > 
template< typename... Args > 
void Signal<Signature>::operator()(Args&&... args) const 
{ 
    for (const Delegate& delegate : _delegates) 
     delegate(std::forward<Args>(args)...); 
} 
+0

'Args && ... args' è fondamentalmente' int && args'. – Nawaz

+0

@Nawaz si, ho notato che l'ha esplicitamente specificato sopra. Non importa quello che ho appena detto. Sì, l'inoltro perfetto funziona principalmente quando si deduce il tipo di modello. –

+0

Ciò che l'OP deve (o _can_) fare è avere un 'Args ...' separato per 'operator()'. –

risposta

5

Il problema è che si sta specificando in modo esplicito il parametro di template come int. Come menzionato da Nawaz, Args&&... viene espanso in int&& e non è possibile associare un lvalue a un riferimento di rvalue.

La ragione di inoltro perfetta funziona è che quando si chiama una funzione (ad esempio) senza specificare gli argomenti di modello, vengono dedotti a uno o &&& e poi il crollo riferimenti (letto di riferimento crollare se non si conosce che cos'è). È esplicitamente specificarlo, quindi si inibisce il riferimento collassando e rovinare tutto.

Una cosa che potreste fare è dare il operator() di essa la propria lista di argomenti di template:

template<typename... Args2> 
void operator()(Args2&&... args) const; 

... 

template< typename... Args > 
template< typename... Args2 > 
void Signal<Args...>::operator()(Args2&&... args) const 
{ 
    for (const Delegate& delegate : _delegates) 
     delegate(std::forward<Args2>(args)...); 
} 

In questo modo si può lasciare riferimento collasso prendersi cura di esso per voi.

+0

È lo stesso problema: ora se dichiaro 's' come' Signal s; 'e quindi lo chiamo come' int i = 10; s (i); 'allora non verrà compilato! – Nawaz

+0

@Nawaz quindi non dichiarare 's' come' Signam ' –

+0

Ma allora qual è il punto? – Nawaz

Problemi correlati