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:
- passando un oggetto assegnato heap dal codice chiamante.
- acquisizione per riferimento nella lambda.
- assicurandosi di non modificare l'originale nel codice chiamante.
- 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.
};
come rendere 'someMethod' un metodo di modello? Non è possibile copiare un oggetto senza il suo tipo. o semplicemente passare 'shared_ptr' attorno a –
@BryanChen un puntatore intelligente funzionerebbe ma stavo cercando di evitarlo. Come i metodi di template. – SaldaVonSchwartz
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. –