C++ C++;lambda:如果由值捕获,如何避免切片引用
我有一个方法,它接受一个参数,该参数是对基类的引用,我通过将方法实现包装在C++ C++;lambda:如果由值捕获,如何避免切片引用,c++,c++11,lambda,c++14,C++,C++11,Lambda,C++14,我有一个方法,它接受一个参数,该参数是对基类的引用,我通过将方法实现包装在队列中来排队调用方法体 问题是,我希望通过值捕获方法的参数,以便队列中的每个lambda都可以使用自己的副本执行 但是,如果我按值捕获,引用参数的lambda副本似乎会对其进行切片,留下一个基类副本,而不是引用中的实际派生类 如果我改为通过引用捕获参数,我确实会在lambda中获得实际的派生类,但是obj可能在方法调用之间超出范围,或者其状态可能会改变 请注意,该方法应该是可重入的,但不是异步或并发的 这是我的意思的一个例
队列中来排队调用方法体
问题是,我希望通过值捕获方法的参数,以便队列中的每个lambda都可以使用自己的副本执行
但是,如果我按值捕获,引用参数的lambda副本似乎会对其进行切片,留下一个基类副本,而不是引用中的实际派生类
如果我改为通过引用捕获参数,我确实会在lambda中获得实际的派生类,但是obj可能在方法调用之间超出范围,或者其状态可能会改变
请注意,该方法应该是可重入的,但不是异步或并发的
这是我的意思的一个例子(省略队列):
是:
到目前为止,我在方法调用中保存派生类型的唯一方法是:
从调用代码传递堆分配的对象
在lambda中通过引用捕获
确保不改变调用代码中的原始代码
最后在方法返回后删除heap obj
像这样:
DerivedObj* obj = new DerivedObj();
someMethod(*obj);
delete obj;
但是我希望能够从调用代码堆栈中传递一个引用,即使在someMethod
内部发生了触发另一个调用someMethod
的事件,也不会有问题
有什么想法吗
我想到了一种方法,但我不确定如何做,就是在“someMethod”中,将参数移动到堆中,执行lambda,然后最终删除它(因为调用方在调用此方法后不会真正使用它)。但我不确定这是否真的有问题(我只是考虑了一下,因为这有点像Objective-C块所做的)
更新:
这是我迄今为止的解决方案:
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()();
}
}
是的,我知道,我用的是原始指针。。。(eeeee vil…:p),但至少我可以使用ref参数保留方法的签名
克隆方法大致如下:
template <class T>
struct ConcreteEvent : public Event {
virtual Event* heapClone() {
return new T(*(T*)this);
}
// .... more stuff.
};
模板
结构ConcreteEvent:公共事件{
虚拟事件*heapClone(){
返回新的T(*(T*)本);
}
//……更多的东西。
};
使用指针作为somethod
参数:
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);
}
如果没有一些干扰性的改变,似乎不可能达到你想要的结果。当前,您有一个调用者可以更改或销毁其对象,而不关心引用是否仍在队列中。对于这种情况,你唯一的选择就是复制一份。创建lambda的函数不知道要传递什么类型的对象,因此不知道如何复制它
有不同的方法可以解决您的问题:您可以通过让调用者持有一个shared\u ptr
并将共享指针复制到lambda中,让调用者知道额外的引用。这解决了生命周期问题,但仍然取决于调用者不修改对象。您还可以让编译器为每个派生类生成不同的排队函数,方法是将该函数作为模板。模板的每个实例都知道如何复制其特定类型。您已经忽略了这两种解决方案。我知道还有一种方法,就是向基类添加一个虚拟克隆函数,在派生类中实现该函数以创建堆副本。而不是[=]{}
对于lambda,您可以执行[DerivedObj obj=obj](){}
这将准确地捕获您想要的内容以及您想要的方式。让someMethod
成为模板方法怎么样?不能复制没有类型的对象。或者只需通过shared\u ptr
around@BryanChen智能指针可以工作,但我试图避免它。与模板方法相同。请查看。我相信用C++ 11或C++ 14标记的C++代码应该认真考虑使用智能指针而不是裸的新的和删除。@使用原始指针的LeBeasa是一个选择的问题,在这种情况下,只是一个建议的解决方案(我不高兴这是为什么我首先问的问题的替代品)。智能指针只是该语言的另一个功能(不像objective-c那样内置在语言中),并且它们不是所有上下文中的正确选择(即,此代码将在紧密的渲染循环中运行)。最后,11和14标记是为了防止11或14规范具有某些防止切片的功能,而不是为了暗示样式。尽管如此,要点如下:)。在c++14中,您可以用std::unique_ptr
替换auto-frozenEvent
,然后通过移动而不是通过值进行捕获。如果您通过值捕获指针,您将得到与通过引用捕获对象相同的结果:您仍然无法获得对象的副本。所以这并不能回答我的问题。啊,是的。我忽略了OP希望为队列中的每个项目获取副本这一事实。我更新了我的问题以显示队列的用例,因为它可能会进一步说明这个问题。到现在为止,我已经修复了这个问题,让这个对象有一个克隆方法,我用它来创建一个堆版本,在这个方法中使用。谢谢我现在选择堆复制方法(如对我的问题的更新中所示)。@SaldaVonSchwartz,如果您静态地知道调用者中的类型,您可以将函数设置为模板。但除此之外,克隆
方法是唯一的方法。您可以尝试使用来处理克隆(它可以处理克隆对象,而无需为它们添加基类,但这是一件需要学习的事情)。@JanHudec试图避开Boost,因为我是故意“从头开始”做这件事的。但是谢谢。@SaldaVonSchwartz从头开始做每件事可能是一次很有教育意义的经历,但是你应该记住,不使用智能指针是自相矛盾的
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()();
}
}
template <class T>
struct ConcreteEvent : public Event {
virtual Event* heapClone() {
return new T(*(T*)this);
}
// .... more stuff.
};
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);
}
obj type:struct DerivedObj
refLambda::obj type:struct DerivedObj
valLambda::obj type:struct DerivedObj