簡體   English   中英

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);

方法是:

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然后最終刪除它(因為調用者在調用它之后不會真正使用它)方法)。 但不確定這實際上是否是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.
};

如果沒有一些侵入性的變化,似乎無法實現您想要的結果。 目前,您有一個調用者可以更改或銷毀其對象,而無需關心引用是否仍在隊列中。 有了這種打電話,您唯一的選擇就是制作副本。 創建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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM