2016-08-02 111 views
8

我在我的應用程序中有一個Signal類,它爲類提供了一個公開事件(與.NET相同)的選項。C++ 11 std ::轉發指針

班級運作良好。

昨天我看到this SO question (and its answer)並且熟悉了std::forward

我決定嘗試在我的代碼中使用它,所以我改爲每std::function<void(Args...)>std::function<void(Args&&...)>,並在提高功能(operator())我使用了我在上面的鏈接看到了同樣的邏輯,所以現在該功能將Args&&...args和回調使用std::forward<Args>(args)...

這裏是我的信號類的簡化版本(含部分變更,使之成爲很好的例子):

template<typename... Args> class Signal 
{ 
public: 
    int operator+=(const std::function<void(Args&&...)>& func) { 
     int token = getNewToken(); 
     m_subscribers.emplace(token, func); 
     return token; 
    } 

    void operator()(Args&&... args) { 
     for (auto it : m_subscribers) { 
      it.second(std::forward<Args>(args)...); 
     } 
    } 

private: 
    std::map<int, std::function<void(Args&&...)>> m_subscribers; 
}; 

int main() { 
    int* six = new int(6); 
    int seven = 7; 

    Signal<int*> e1; 
    e1 += [](int* x) { std::cout << *x; }; 

    Signal<int> e2; 
    e2 += [](int x) { std::cout << x; }; 

    e1(&seven); 
    e2(6); 

    e1(six); //Error C2664 'void Signal<int *>::operator()(int *&&)': 
       // cannot convert argument 1 from 'int *' to 'int *&&' 
    e1(std::move(six)); //This is a workaround   
    return 0; 
} 

我看到的問題是與類(或main在此示例中)嘗試用普安特來舉辦活動rs和我不知道如何解決這個問題。

我的主要目標是讓Signal類成爲一個通用API,如果開發人員選擇使用Signal<int*>我不希望他用std::move加註。

我在做什麼錯在這裏?

回答

11

T&&只是一個通用參考,如果T是一個非cv限定的函數模板參數。在您的電話運營商:

void operator()(Args&&... args) { 

Args是不是函數的模板參數,它是類的模板參數。因此,對於Signal<int*>,此operator()取右值參考int*。由於six是一個左值,因此失敗。

你想要的是給Signal提供正確的參考資格。像這樣:

template<typename... Args> 
class Signal 
{ 
    using F = std::function<void(Args...)>; // NB: Just Args... 
public: 
    int operator+=(F func) { 
     int token = getNewToken(); 
     m_subscribers.emplace(token, std::move(func)); 
     return token; 
    } 

    void operator()(Args... args) {  // NB: just Args... 
     for (auto& it : m_subscribers) { // NB: auto& 
      it.second(args...); 
     } 
    } 

private: 
    std::map<int, F> m_subscribers; 
}; 

請注意,轉發Args...無論如何都是有問題的。如果你有兩個用戶呢?一旦你轉發了參數,一旦你不能再真正使用它們。

以上將使Signal<int*>做你所期望的。 operator()只需要一個int*,您可以將左值或右值傳遞給。

+2

@WernerErasmus是的。如果您有多個訂戶,則無法多次轉發相同的參數。 – Barry

+0

感謝Barry,除了'使用F'部分,這是我已經使用的代碼。所以基本上你的答案是「你不能也不應該在這種情況下使用std :: forward」? – ZivS

+0

我更關心如何以及如果我可以使用所有類型的std :: forward。我覺得我應該多讀一些,因爲你的答案的第一段對我來說有點核心...... – ZivS

1

巴里的答案是正確的,但可能不是那麼清楚地解釋。

&&只有在模板參數扣除發生時才作爲轉發(或「通用」)參考的特殊處理。但是這裏存在不扣:

Signal<int*> e1; // `Args...` is explicitly `int*` 
... 
e1(six);   // `Args...` *has already been specified* 

當模板類實例化,它們基本上轉化爲正常類,只是碰巧被編譯器寫入。請參閱this answer以查看以C++代碼寫出的內容的示例。

在C++ 14,有沒有引發的類模板參數推導沒有輔助功能(不是構造)方式:

template <typename Args...> 
Signal<Args...> make_signal(Args&&...) { return Signal<Args...>; } 

....不過需要注意的那在你的情況下,這是毫無意義的:你不想推斷你創建的參數的類型爲Signal,你想要指定他們提前。

(請注意,在C++ 17,有template argument deduction of class templates支持。我認爲,這意味着,這將是可能forward模板類的參數,但它不是立即清楚我在做什麼的影響這樣的事情將是)

你想要允許的是參數將被轉發在通話時間。這實際上是相當簡單:

template<typename... Args> class Signal 
{ 
public: 
    // .... skipping some code... 

    template <typename... CallArgs> 
    void operator()(CallArgs&&... args) { 
     callback(std::forward<CallArgs>(args)...); 
    } 
}; 

....但同樣,你的情況這並不完全意義,因爲在巴里的回答說。如果您有多個回調,您不需要轉發參數,以防止移動並重新使用它們。

可以通過檢查m_subscribers的大小和僅使用forward的代碼(如果它的代碼爲1)並僅以其他方式傳遞參數來解決此問題。但是,這可能會導致混淆行爲,因爲調用回調的方式通常不應取決於您的對象的狀態。所以,你也許可以寫一個單獨的類,SingletonSignal,回調即必須forward版參數調用(例如,如果回調想要一個不可複製對象,如unique_ptr所有權轉讓)。