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