C++lambda:如果引用被值捕获,如何避免对其进行切片
C++ lambda: how to avoid slicing a reference if captured by value
我有一个方法,它接受一个参数,该参数是对基类的引用,我通过将方法实现封装在queue<function<void()>>
中来将方法体的调用排队
问题是,我希望通过值捕获方法的参数,这样队列中的每个lambda都可以用自己的副本执行。
但是,如果我按值捕获,则引用参数的lambda副本似乎会对其进行切片,从而留下基类副本,而不是引用中的实际派生类。
如果我改为通过引用捕获参数,那么我确实会在lambda中获得实际的派生类,但obj可能会在方法调用之间超出范围,或者它的状态可能会更改。
请注意,该方法应该是可重入的,但既不是异步的,也不是并发的。
这是我的意思的一个例子(省略队列):
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();
}
调用方法时的输出如下:
DerivedObj obj{};
someMethod(obj);
Is:
obj type:10DerivedObj
refLambda::obj type:10DerivedObj
valLambda::obj type:7BaseObj
到目前为止,我设法在方法调用中保留派生类型的唯一方法是:
- 从调用代码传递堆分配的对象
- 通过lambda中的引用捕获
- 确保调用代码中的原始代码不会发生变异
- 最后在方法返回后删除堆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.
};
如果没有一些侵入性的更改,似乎不可能实现您想要的结果。目前,您有一个调用程序,它更改或销毁其对象,而不关心引用是否仍在队列中。对于这种经常打电话的人,你唯一的选择就是复印一份。创建lambda的函数不知道要传递什么类型的对象,所以它不知道如何复制它
有不同的方法可以解决您的问题:您可以通过让调用方持有shared_ptr
并将共享指针复制到lambda中来让它知道额外的引用。这解决了使用寿命问题,但仍然取决于调用方不修改对象。您还可以让编译器为每个派生类生成不同的入队函数,方法是将该函数作为模板。模板的每个模板实例都知道如何复制其特定类型。你已经否定了这两种解决方案。我只知道还有一种方法,那就是向基类添加一个虚拟克隆函数,在派生类中实现该函数以创建堆副本。
您可以执行[DerivedObj obj=obj](){}
,而不是为lambda执行[=]{}
,它将准确地捕获您想要的内容。
使用指针作为someMethod
参数:
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);
}
在VS2013中测试,它将打印:
obj type:struct DerivedObj
refLambda::obj type:struct DerivedObj
valLambda::obj type:struct DerivedObj
- C++避免重复声明的语法是什么
- 在没有太多条件句的情况下,我如何避免被零除
- 如何重构类层次结构以避免菱形问题
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 以下示例中如何避免代码复制?C++/库达
- Python中的for循环与C++有何不同
- 如何确保在使用基于布尔值的两个方法之一调用方法时避免分支预测错误
- 避免矢量中的对象切片<Base><shared_ptr>
- 专门化模板覆盖函数/避免对象切片
- 克隆设计模式适配器 - 避免切片子项(类似于原型模式)
- 避免对象切片
- 按引用传递可避免对象切片
- C++lambda:如果引用被值捕获,如何避免对其进行切片
- 缺少复制构造函数与对象切片有何关系
- 避免Python样式切片的陷阱
- 避免在方法调用中进行对象切片
- 按引用传递总是避免切片问题吗?
- 避免对象切片c++
- 避免对象切片和使用shared_ptr