C ++ lambda:如果按值

时间:2016-01-13 02:36:48

标签: 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。
  5. 像这样:

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

    但是我希望能够从调用代码堆栈中传递一个引用,即使在someMethod内部发生触发另一个someMethod调用的内容时也没问题。

    有什么想法吗?

    我想到的一种方法,但是我不知道怎么做,在`someMethod&#39;里面,将参数移到堆中,执行lambda然后最后删除它(因为调用者赢了&# 39;在调用此方法后真的使用它)。但不确定这实际上是不是很糟糕(我只考虑过它,因为它有点像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.
    };
    

3 个答案:

答案 0 :(得分:0)

在没有一些侵入性变化的情况下,似乎无法实现您想要的结果。目前,您有一个调用者可以更改或销毁其对象,而无需关心引用是否仍在队列中。有了这种打电话,您唯一的选择就是制作副本。创建lambda的函数不知道您要传递的对象类型,因此它不知道如何复制它。

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

答案 1 :(得分:0)

而不是[=]{}为你的lambda,你可以做[DerivedObj obj=obj](){},它将准确捕捉你想要的东西。

答案 2 :(得分:-2)

将指针用作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