2012-01-03 73 views
42

在我的C++應用程序(使用Visual Studio 2010),我需要存儲一個std ::功能,如:我應該複製一個std :: function還是我可以總是參考它?

class MyClass 
    { 
    public: 
     typedef std::function<int(int)> MyFunction; 
     MyClass (Myfunction &myFunction); 
    private: 
     MyFunction m_myFunction; // Should I use this one? 
     MyFunction &m_myFunction; // Or should I use this one? 
    }; 

正如你所看到的,我加了函數參數的構造函數的引用。

但是,在我的課程中存儲函數的最佳方法是什麼?

  • 我可以存儲函數作爲參考,因爲std :: function只是一個函數指針,函數的'可執行代碼'保證留在內存中嗎?
  • 如果lambda傳遞並且調用者返回,我是否必須複製副本?

我的直覺告訴我們,存儲引用(甚至是const引用)是安全的。我期望編譯器在編譯時爲lambda生成代碼,並在應用程序運行時將此可執行代碼保存在「虛擬」內存中。因此,可執行代碼從不「刪除」,我可以安全地存儲對其的引用。但這是真的嗎?

+3

不管std :: function包含什麼......如果你保存一個被引用的引用超出範圍的引用,你就會遇到問題。 – Xeo 2012-01-03 11:16:43

+0

@Alex包裝函數對象的類型lambda或其他,沒有區別。這裏適用於任何可以形成參考(包括指針)的對象。所以,如果你稍後嘗試調用(A)'std :: function'通過'std :: reference_wrapper'傳遞一個函數,其中被引用的函數的生存期已經結束,並且/或者(B)對引用的生命週期結束的'std :: function'。 – 2016-07-17 14:58:00

回答

40

由於std :: function只是一個函數指針,並且函數的'可執行代碼'保證保留在內存中,我可以將函數作爲參考存儲嗎?

std::function不僅僅是一個函數指針。它是任意可調用對象的包裝器,並管理用於存儲該對象的內存。與其他任何類型一樣,只有當有其他方法可以保證引用的對象在使用該引用時仍然有效時,纔可以保存引用

除非您有充分的理由來存儲參考,並且有一種方法可以保證它的有效性,否則請按價值進行存儲。

傳遞const對構造函數的引用是安全的,並且可能比傳遞值更有效。通過非const引用傳遞是一個壞主意,因爲它阻止了你傳遞臨時對象,所以用戶不能直接傳遞lambda,bind的結果或除std::function<int(int)>本身之外的任何其他可調用對象。

+0

確實。我只是意識到(感謝你的回答)std :: function是函數指針的包裝,即使函數或lambda保留在內存中,它的包裝也不一定是。可能(可能?)編譯器在將lambda傳遞給我的構造函數時即時生成std :: function,這意味着它在調用之後確實消失了。謝謝。 – Patrick 2012-01-03 11:25:08

+4

按值傳遞給構造函數,然後std :: move到成員中。 http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ – 2013-08-26 18:26:27

+4

「做沒有工作比做一些工作要好 - 走向本土2013」​​。創建對象(1工作),通過const ref(不工作),使用(1工作)。創建對象(1工作),按值傳遞和移動(1工作),使用(1工作)。所以在這種情況下,通過const ref傳遞更好。 – Jagannath 2013-09-12 22:28:28

2

複製儘可能多的,你喜歡。它是可複製的。標準庫中的大多數算法都需要函子。

但是,在非平凡的情況下,通過引用傳遞可能會更快,所以我建議通過常量引用傳遞並按值存儲,因此您不必關心生命週期管理。所以:

class MyClass 
{ 
public: 
    typedef std::function<int(int)> MyFunction; 
    MyClass (const Myfunction &myFunction); 
      // ^^^^^ pass by CONSTANT reference. 
private: 
    MyFunction m_myFunction; // Always store by value 
}; 

通過由常數或右值引用傳遞你保證你不會修改功能,同時你還可以把它調用者。這可以防止您錯誤地修改該函數,並且通常應該避免故意執行該操作,因爲它的可讀性低於使用返回值。

編輯:我原本上面說過「常數或右值」,但戴夫的評論讓我看起來並且右值引用不接受左值。

+2

Uhhh ...傳遞右值引用強制用戶將它移動到你的位置 - 絕對不是「保證你不會修改函數」。另外,更現代的做法是,如果你打算存儲它(並將它'std :: move'到你存儲的成員中),或者如果你不打算存儲它,則通過常量引用傳遞值。 – David 2012-07-09 12:39:13

+0

@Dave:你說得對。所以如果你需要一個函數來取得像方法那樣的l或rvalue函數,它仍然是一件痛苦的事情。呸。 – 2012-07-23 09:47:13

2

我會建議你做一個副本:

MyFunction m_myFunction; //prefferd and safe! 

它是安全的,因爲如果原來的對象超出範圍破壞本身的副本將仍然在類的實例存在。

7

如果您通過引用將函數傳遞給構造函數,並且不復制該函數的副本,那麼當函數超出此範圍之外的範圍時,您將失去運氣,因爲參考不會更有效。以前的答案已經說了很多。

我想添加的是,相反,您可以將而不是引用的函數傳遞給構造函數。爲什麼?好吧,無論如何你都需要它的一個副本,所以如果你按值傳遞的話,編譯器可以優化臨時傳入時(例如就地編寫的lambda表達式)複製副本的需要。

當然,無論您做什麼,當您將傳入函數分配給變量時,您可能會創建另一個副本,因此請使用std::move來消除該副本。例如:

class MyClass 
{ 
public: 
    typedef std::function<int(int)> MyFunction; 

    MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction)) 
     {} 

private: 
    MyFunction m_myFunction; 
}; 

所以,如果他們在一個右值以上通過,編譯器優化掉第一拷貝到構造,和std ::此舉消除了第二個:)

如果(只)構造函數使用const引用,你需要做出的一個副本的功能,無論它是如何傳入

另一種方法是定義兩個構造函數,來分別處理左值和右值。

class MyClass 
{ 
public: 
    typedef std::function<int(int)> MyFunction; 

    //takes lvalue and copy constructs to local var: 
    MyClass (const Myfunction & myFunction): m_myfunction(myFunction) 
     {} 
    //takes rvalue and move constructs local var: 
    MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction)) 
     {} 

private: 
    MyFunction m_myFunction; 
}; 

現在,您可以通過不同的方式進行不同的右移操作,並且通過顯式處理它(而不是讓編譯器爲您處理)來消除在此情況下複製的需要。可能比第一個更有效率,但也是更多的代碼。

的(可能看到一個公平的位在這裏)相關的參考(和一個非常良好的閱讀): http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

3

作爲一般規則(特別是如果你使用這些對於一些高度線程化的系統),通按價值。真的沒有辦法在一個線程中驗證底層對象仍然在引用類型中,所以你打開了自己的比賽和死鎖錯誤。

另一個考慮因素是std :: function中的任何隱藏狀態變量,對於它們來說修改不太可能是線程安全的。這意味着,即使底層函數調用是線程安全的,std :: function包裝器的「()」調用可能不會。您可以通過始終使用std :: function的線程本地副本來恢復所需的行爲,因爲它們每個都有一個狀態變量的獨立副本。

相關問題