2012-12-14 11 views
15

Sono arrivato a C++ 11 da uno sfondo Objective-C, e una cosa che sto cercando di fare i conti è la diversa semantica di cattura di C++ 11 lambdas vs Objective-C "blocks". (Vedere here per un confronto).Uso di C++ 11 lambda in modo asincrono, in modo sicuro

In Objective-C, come C++, il puntatore self/this viene catturato in modo implicito se si fa riferimento a una variabile membro. Ma poiché tutti gli oggetti in Objective-C sono effettivamente "puntatori condivise", per usare la terminologia C++, si può fare questo:

doSomethingAsynchronously(^{ 
    someMember_ = 42; 
}); 

... e avrete la garanzia che l'oggetto di cui si sta accedendo membro sarà vivo quando il blocco verrà eseguito. Non devi pensarci. L'equivalente in C++ sembra essere qualcosa di simile:

// I'm assuming here that `this` derives from std::enable_shared_from_this and 
// is already owned by some shared_ptr. 
auto strongThis = shared_from_this(); 

doSomethingAsynchronously([strongThis, this] { 
    someMember_ = 42; // safe, as the lambda holds a reference to this 
         // via shared_ptr. 
}); 

Qui, è necessario ricordare per catturare l'shared_ptr oltre al puntatore this. C'è un modo meno incline agli errori di raggiungere questo?

+3

* "Non devi pensarci." * Comincia a pensarci. Porta a un design migliore. – Pubby

+0

@Pubby Ma il fatto è che è senza sforzo usare i blocchi che li rende così utili e così pervasivi da eseguire compiti asincroni one-shot nel mondo Obj-C. Se avessero la semantica C++ 11, e dovevi chiedervi "questo oggetto sarà vivo, questo oggetto sarà vivo, questo oggetto sarà vivo ..." ogni volta, penso che molte persone sarebbero tentate di dì "fanculo, lo farò in modo sincrono". –

+0

Creare un puntatore condiviso da 'this' non garantisce che esisterà ancora, a meno che l'oggetto stesso non sia già di proprietà di un puntatore condiviso e lo si copi. La creazione di un nuovo puntatore condiviso (con 'new shared_ptr (this)' o 'make_shared (this)') servirà solo per ottenere una doppia eliminazione, a meno che la memoria non sia trapelata altrimenti. Quindi, nel tuo caso, in che modo "questo" viene cancellato se non si crea un puntatore condiviso a questo punto? – Agentlien

risposta

6

Uno dei principi di base del C++ è che non si paga per ciò che non si usa. Ciò significa che in questo caso i contesti in cui prendere uno a this non è necessario non dovrebbero comportare alcun sovraccarico di conteggio dei riferimenti. Ciò significa anche che non dovrebbe accadere automaticamente anche ad es. come caratteristica di enable_shared_from_this, poiché si potrebbe voler passare un lambda di breve durata ad un algoritmo (for_each, ecc.) nel qual caso il lambda non sopravvive al suo scopo.

Suggerirei di adattare lo lambda-wrapper pattern; in tal caso viene utilizzato per move cattura di un oggetto di grandi dimensioni (How to capture std::unique_ptr "by move" for a lambda in std::for_each), ma esso può anche essere utilizzato per la cattura condiviso della this:

template<typename T, typename F> 
class shared_this_lambda { 
    std::shared_ptr<T> t; // just for lifetime 
    F f; 
public: 
    shared_this_lambda(std::shared_ptr<T> t, F f): t(t), f(f) {} 
    template<class... Args> 
    auto operator()(Args &&...args) 
    -> decltype(this->f(std::forward<Args>(args)...)) { 
    return f(std::forward<Args>(args)...); 
    } 
}; 

template<typename T> 
struct enable_shared_this_lambda { 
    static_assert(std::is_base_of<std::enable_shared_from_this<T>, T>::value, 
    "T must inherit enable_shared_from_this<T>"); 
    template<typename F> 
    auto make_shared_this_lambda(F f) -> shared_this_lambda<T, F> { 
    return shared_this_lambda<T, F>(
     static_cast<T *>(this)->shared_from_this(), f); 
    } 
    template<typename F> 
    auto make_shared_this_lambda(F f) const -> shared_this_lambda<const T, F> { 
    return shared_this_lambda<const T, F>(
     static_cast<const T *>(this)->shared_from_this(), f); 
    } 
}; 

Uso derivandola enable_shared_this_lambda oltre a enable_shared_from_this; si può quindi chiedere espressamente che eventuali lambda longevi prendono una condivisa this:

+0

Sì, ma è necessario avere anche un membro M_ atomico o mutex se i thread sono coinvolti. –

+0

Grazie, penso che adotterò questo schema. Non sono ancora del tutto convinto che sia una cosa buona che shared_ptr sia una funzionalità a livello di libreria dato che lo rende così ingombrante, ma sono le interruzioni, immagino. –

2

In realtà, c'è una risposta giusta a questo problema . La risposta ha lo stesso effetto di legare con shared_from_this() (come quando lo fai con boost::asio::io_service). Pensaci; cosa fa il binding con shared_from_this()? Sostituisce semplicemente this. Quindi, cosa ti impedisce di sostituire this con shared_from_this() totalmente?

Seguendo il tuo esempio, che ho aggiornato a fare la differenza più chiara, invece di questo:

auto strongThis = shared_from_this(); 

doSomethingAsynchronously([strongThis, this]() { 
    this->someMember_ = 42; //here, you're using `this`... that's wrong! 
}); 

fare questo:

auto strongThis = shared_from_this(); 

doSomethingAsynchronously([strongThis]() //notice, you're not passing `this`! 
{ 
    strongThis->someMember_ = 42;    
}); 

L'unico costo è che dovrai a prefisso tutto con strongThis->. Ma questo è il modo più significativo per farlo.