【发布时间】:2016-01-13 02:36:47
【问题描述】:
我有一个方法,它接受一个参数,该参数是对基类的引用,我通过将方法实现包装在 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);
是:
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,然后最终删除它(因为调用者不会真正使用它调用此方法后)。但不确定这是否真的是 hacky(我只是想它,因为这有点像 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()();
}
}
是的,我知道,我正在使用原始指针...(eeeeevil ....:p),但至少我可以使用 ref 参数保留方法的签名。
克隆方法大致如下:
template <class T>
struct ConcreteEvent : public Event {
virtual Event* heapClone() {
return new T(*(T*)this);
}
// .... more stuff.
};
【问题讨论】:
-
如何将
someMethod设为模板方法?您无法复制没有类型的对象。或者只是传递shared_ptr -
@BryanChen 智能指针可以工作,但我试图避免它。与模板方法相同。
-
请看C++ Core Guidelines。我认为用 C++11 或 C++14 标记的 C++ 代码应该认真考虑使用智能指针而不是裸 new 和 delete。
-
@LebenAsa 使用原始指针是一个选择问题,在这种情况下,只有一个建议的解决方案(我对此不满意,这就是为什么我首先要询问替代方案的问题) .智能指针只是该语言的一个特性(与 Objective-c 不同,它们内置于语言中)并且它们不是在所有上下文中的正确选择(即,此代码将在紧密的渲染循环中运行)。最后,11 和 14 标签是为了防止 11 或 14 规范具有一些防止切片的功能,而不是暗示样式。仍然,要点:)。
-
在 c++14 中,您可以将
auto frozenEvent替换为std::unique_ptr<Event>,然后通过移动而不是按值进行捕获。