2016-01-13 6 views
7

Ho un metodo che accetta un parametro che è un riferimento a una classe base e Enqueue invocazioni del corpo del metodo avvolgendo l'implementazione del metodo in un queue<function<void()>>C++ lambda: come evitare affettare un riferimento se catturato dal valore

Il problema è che speravo di acquisire il parametro del metodo in base al valore in modo che ogni lambda nella coda potesse quindi eseguire con la propria copia.

Ma se catturo per valore, la copia lambda del parametro di riferimento sembra tagliarlo, lasciandomi una copia della classe base invece della classe derivata effettiva nel riferimento.

Se catturo il parametro per riferimento, invece, ottengo la classe derivata effettiva nel lambda ma l'oggetto può passare dall'ambito tra le invocazioni del metodo o il suo stato può cambiare.

Attenzione, il metodo dovrebbe essere rientranti ma non asincroni né concomitanti.

Questo è un esempio di quello che voglio dire (omettendo la coda):

struct BaseObj { 
    virtual ~BaseObj() = default; 
}; 

struct DerivedObj : public BaseObj { 

}; 

void someMethod(BaseObj& obj) { 

    // obj is of type BaseObj: 
    std::cout << "\nobj type:" << typeid(obj).name(); 

    auto refLambda = [&] { 
     // captured obj is of type DerivedObj: 
     std::cout << "\nrefLambda::obj type:" << typeid(obj).name(); 
    }; 

    auto valLambda = [=] { 
     // captured obj is of type BaseObj: 
     // presumably because it was copied by value, which sliced it. 
     std::cout << "\nvalLambda::obj type:" << typeid(obj).name(); 
    }; 

    refLambda(); 
    valLambda(); 
} 

L'uscita quando si chiama il metodo in questo modo:

DerivedObj obj{}; 
someMethod(obj); 

Is:

obj type:10DerivedObj 
refLambda::obj type:10DerivedObj 
valLambda::obj type:7BaseObj 

A partire da ora l'unico modo in cui sono riuscito a conservare il tipo derivato nelle invocazioni del metodo è il seguente:

  1. passando un oggetto assegnato heap dal codice chiamante.
  2. acquisizione per riferimento nella lambda.
  3. assicurandosi di non modificare l'originale nel codice chiamante.
  4. infine l'eliminazione dell'oggetto heap dopo il ritorno del metodo.

Ti piace questa:

DerivedObj* obj = new DerivedObj(); 
    someMethod(*obj); 
    delete obj; 

Ma speravo di essere in grado di passare solo un riferimento dalla pila codice chiamante e andare bene anche se all'interno someMethod succede qualcosa che innesca un'altra chiamata a someMethod.

Qualche idea?

Un approccio a cui pensavo, ma non sono sicuro di come si farebbe, all'interno di "someMethod", spostando il parametro sull'heap, eseguendo il lambda e infine cancellandolo (poiché il chiamante non utilizzerà realmente dopo aver chiamato questo metodo). Ma non sono sicuro che sia effettivamente hacky (ci ho pensato solo perché è un po 'come i blocchi Objective-C).

aggiornamento:

Questa è la soluzione che ho finora:

void Object::broadcast(Event& event) { 
    auto frozenEvent = event.heapClone(); 

    auto dispatchBlock = [=]() { 
     for (auto receiver : receivers) { 
      receiver.take(event); 
     } 

     delete frozenEvent; 
     _private->eventQueue.pop(); 
     if (!_private->eventQueue.empty()) { 
      _private->eventQueue.front()(); 
     } 
    }; 

    _private->eventQueue.push(dispatchBlock); 
    if (_private->eventQueue.size() == 1) { 
     _private->eventQueue.front()(); 
    } 
} 

Sì, lo so, sto usando puntatori prime ... (eeeeevil ....: p), ma almeno posso mantenere la firma del metodo con il parametro ref.

Il metodo clone è lungo le linee di questo:

template <class T> 
struct ConcreteEvent : public Event { 
    virtual Event* heapClone() { 
     return new T(*(T*)this); 
    } 

    // .... more stuff. 
}; 
+0

come rendere 'someMethod' un metodo di modello? Non è possibile copiare un oggetto senza il suo tipo. o semplicemente passare 'shared_ptr' attorno a –

+0

@BryanChen un puntatore intelligente funzionerebbe ma stavo cercando di evitarlo. Come i metodi di template. – SaldaVonSchwartz

+0

Si prega di dare un'occhiata a [C++ Core Guidelines] (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md). Credo che il codice C++ taggato con C++ 11 o C++ 14 dovrebbe prendere seriamente in considerazione l'utilizzo di un puntatore intelligente invece di nuovo nudo ed eliminare. –

risposta

0

E ' non sembra possibile ottenere il risultato desiderato senza alcune modifiche invadenti. Attualmente, hai un chiamante che modifica o distrugge il suo oggetto senza preoccuparsi se il riferimento è ancora in coda. Con questo tipo di chiamante, la tua unica scelta è di fare una copia. La funzione che crea il lambda non sa quale tipo di oggetto tu passi, quindi non sa come copiarlo.

Ci sono diversi approcci per risolvere il problema: è possibile rendere il chiamante consapevole del riferimento aggiuntivo tenendo premuto uno shared_ptr e copiando il puntatore condiviso nel lambda. Questo risolve il problema della durata, ma dipende comunque dal chiamante per non modificare l'oggetto. Si potrebbe anche fare in modo che il compilatore generi diverse funzioni di accodamento per ogni classe derivata rendendo tale funzione un modello. Ogni istanza del modello del modello sa come copiare il suo tipo specifico. Hai già eliminato entrambe queste soluzioni. C'è solo un altro approccio che conosco, che consiste nell'aggiungere una funzione clone virtuale alla classe base implementata nelle classi derivate per creare una copia heap.

+0

Sembra gradito. Grazie. Sono andato per l'approccio di copia heap per ora (come mostrato nell'aggiornamento alla mia domanda). – SaldaVonSchwartz

+0

@SaldaVonSchwartz, se si conoscesse il tipo staticamente nel chiamante, si potrebbe rendere la funzione un modello. Ma altrimenti il ​​metodo "clone" è l'unico modo. Potresti provare a usare [Boost.Type Erasure] (http://www.boost.org/doc/libs/1_60_0/doc/html/boost_typeerasure.html) per gestire la clonazione (può gestire gli oggetti della clonazione senza aggiungere la classe base per loro, ma è un bel po 'di roba da imparare). –

+0

@JanHudec sta cercando di stare alla larga da Boost visto che sto facendo queste cose "da zero" apposta. Ma grazie. – SaldaVonSchwartz

-2

Usa puntatore come someMethod argomento invece:

void someMethod(BaseObj* obj) { 
    std::cout << "\nobj type:" << typeid(*obj).name(); 
    auto refLambda = [&] { 
     std::cout << "\nrefLambda::obj type:" << typeid(*obj).name(); 
    }; 

    auto valLambda = [=] { 
     std::cout << "\nvalLambda::obj type:" << typeid(*obj).name(); 
    }; 

    refLambda(); 
    valLambda(); 
} 

int main() { 
    DerivedObj obj; 
    someMethod(&obj); 
} 

Testato in VS2013 Sarebbe stampa:

obj type:struct DerivedObj 
refLambda::obj type:struct DerivedObj 
valLambda::obj type:struct DerivedObj 
+3

Se si acquisisce il puntatore in base al valore, si ottiene lo stesso risultato ottenuto se si cattura l'oggetto per riferimento: non si ottiene ancora una copia dell'oggetto. Quindi questo non risponde alla domanda a mio parere. –

+0

Ah sì. Ho perso il fatto che OP vuole acquisire copia per ogni articolo in coda. –

+0

Ho aggiornato la mia domanda per mostrare il caso d'uso con la coda, in quanto potrebbe ulteriormente illustrare il problema. A partire da ora l'ho risolto facendo in modo che l'oggetto in questione avesse un metodo clone che utilizzo per creare una versione heap di esso per l'utilizzo all'interno del metodo. – SaldaVonSchwartz

Problemi correlati