C++ lambda : how to avoid slicing a reference if captured by value

标签 c++ c++11 lambda c++14

我有一个方法,它采用一个参数,该参数是对基类的引用,我通过将方法实现包装在 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

到目前为止,我设法在方法调用中保留派生类型的唯一方法是:

  1. 从调用代码传递堆分配对象。
  2. 在 lambda 中通过引用捕获。
  3. 确保不改变调用代码中的原始代码。
  4. 最终在方法返回后删除堆obj。

像这样:

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

但我希望能够从调用代码堆栈中传递一个引用,即使在 someMethod 中也没有问题。发生的事情触发了对 someMethod 的另一个调用.

有什么想法吗?

我想到但不确定该怎么做的一种方法是,在 `someMethod' 中,将参数移动到堆中,执行 lambda,然后最后删除它(因为调用者不会真正使用它调用此方法后)。但不确定这是否真的有问题(我只是想到它,因为这有点像 Objective-C block 所做的)。

更新:

这是我目前的解决方案:

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.
};

最佳答案

如果不进行一些侵入性的更改,似乎不可能获得您想要的结果。目前,您有一个调用者更改或销毁其对象而不关心引用是否仍在队列中。对于这种经常来电的人,您唯一的选择就是制作拷贝。创建 lambda 的函数不知道您要传递什么类型的对象,因此它不知道如何复制它。

有不同的方法可以解决您的问题:您可以让调用者知道额外的引用,方法是让它持有一个 shared_ptr 并将共享指针复制到 lambda 中。这解决了生命周期问题,但仍然取决于调用者不修改对象。您还可以让编译器为每个派生类生成不同的排队函数,方法是将该函数作为模板。模板模板的每个实例都知道如何复制其特定类型。您已经驳回了这两种解决方案。我只知道另一种方法,即向您在派生类中实现的基类添加虚拟克隆函数以创建堆拷贝。

关于C++ lambda : how to avoid slicing a reference if captured by value,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34757471/

相关文章:

c++ - C++中的变量到底是什么?

c++ - 在 C++ 中使用结构作为映射中的值

c++ - 在 C++ 中,我可以在不生成整个数据结构的情况下查找数据结构吗?

C++:使用 auto 还是 typedef?

java - 如何为特定字符串编写自定义 Java 比较器?

c++ - 使用 Qt 在后台线程中定期执行某些 lambda func 的正确方法是什么?

java - 如何在 BiConsumer lambda 函数中获取 "Object cannot be converted"?

c++ - 托管 C++ 中托管对象的分配

c++ - std::map 发现在 C++ 中不起作用

c++ - 从 'A&'类型的右值无效初始化 'A'类型的非const引用