2016-12-12 47 views
8

所以我在這裏問了一個問題:Lambda Works on Latest Visual Studio, but Doesn't Work Elsewhere,而我得到的迴應,我的代碼被執行,因爲定義的標準的25.1 algorithms.general] 10說:有沒有原因標準算法按價值取Lambdas?

除非另有規定,即採取函數對象算法因爲允許參數自由地複製這些函數對象 。對他們來說,程序員對象的身份是很重要的應該考慮使用指向作爲reference_wrapper<T>

我只是想一個原因,這種情況正在發生一個noncopied實現對象這樣的 包裝類?我們被告知我們的整個生命通過引用來收集對象,那麼爲什麼標準是通過價值來獲取函數對象,甚至在我的鏈接問題中製作這些對象的副本呢?有沒有我不明白這樣做的好處?

+2

另請參閱:http://stackoverflow.com/questions/34825552/passing-function-objects-into-std-algorithms-by-reference – NathanOliver

+0

[sizeof on a stateless lamb返回1](http:// coliru。 stacked-crooked.com/a/ef6ae126ba68338b) – jaggedSpire

+0

複製不包含數據的lambda的成本是多少?優化後,什麼也沒有。在複製elision優化之後,複製甚至是複雜函子的成本可能爲零。 –

回答

6

std假設函數對象並且迭代器可以自由複製。

std::ref提供了一種方法來將函數對象轉換爲具有兼容的operator()的僞引用,該引用使用引用而不是值語義。所以沒有什麼大的價值會丟失。

如果你被教導過你所有的生活,以參考的方式提取對象,請重新考慮。除非有充分的理由,否則以價值爲目標。對價值的推理要容易得多;引用是指向程序中任何位置的任何狀態的指針。

引用的常規用法,作爲指向本地對象的指針,在使用它的上下文中沒有被任何其他活動引用引用,這不是某人閱讀您的代碼,也不是編譯器可以推測的。如果你以這種方式推理引用,它們不會爲你的代碼增加一些荒謬的複雜性。

但是,如果你以這種方式推斷他們,那麼當你的假設被違反時,你將會有錯誤,並且他們會微妙,粗暴,意外和可怕。

一個典型的例子是operator=的數量,當this和參數引用同一個對象時會中斷。但是任何帶有兩個引用或同一類型指針的函數都有相同的問題。

但即使一個參考可能會破壞您的代碼。我們來看看sort。在僞代碼:

void sort(Iterator start, Iterator end, Ordering order) 

現在,讓我們訂購的參考:

void sort(Iterator start, Iterator end, Ordering const& order) 

這件怎麼樣?

std::function< void(int, int) > alice; 
std::function< void(int, int) > bob; 
alice = [&](int x, int y) { std:swap(alice, bob); return x<y; }; 
bob = [&](int x, int y) { std:swap(alice, bob); return x>y; }; 

現在,打電話sort(begin(vector), end(vector), alice)

每次調用<時,所指的order對象交換含義。現在,這是非常荒謬的,但是當你通過const&Ordering,優化必須考慮到這種可能性,並排除它在你的訂貨代碼每invokation!

你不會做以上(實際上這個特定的實現是UB,因爲這將違反有關std::sort任何合理的必要條件);但是編譯器必須證明你每次執行order或調用它時都沒有「做那樣的事」(更改ordering中的代碼)!這意味着不斷重新加載order的狀態,或內聯並證明你沒有任何精神錯亂。

按值時服用這樣做是一個數量級的更硬(並且基本上需要像std::ref)。優化器有一個函數對象,它是本地的,它的狀態是本地的。存儲在其中的任何內容都是本地的,編譯器和優化器知道誰可以合法修改它。

您編寫的每個函數都會保留其「本地範圍」(稱爲C庫函數)的const&,它不能假定const&的狀態在返回後保持不變。它必須從指針指向的任何地方重新加載數據。現在

,我沒有說按值傳遞,除非有很好的理由。有很多很好的理由。例如,您的類型移動或複製的費用非常高,這是一個很好的理由。您正在向其寫入數據。你實際上希望它隨着你每次閱讀而改變。等

但默認的行爲應該是傳遞的價值。只有在有充分理由的情況下才會轉向參考,因爲成本是分散的,難以確定。

+1

'你的類型是非常昂貴的移動或複製,例如,是一個很好的理由'在哪裏畫線?如何處理其他POD的大型POD結構,其中沒有一個分配在堆上? – johnbakers

+2

@johnbakers當用戶bit and並分析時,已經證明它是您的副本,而不是您的IO或您的低效算法,或者緩存耗盡或...(等等),這是瓶頸。即幾乎從不。 –

+0

@johnbakers你應該很少傳遞其他POD結構的大POD結構。這將在你的代碼基數的一小部分的一小部分;如果你的函數取決於整個POD結構的狀態,你的代碼已經被搞亂了。如果不是,你不應該傳遞那個POD結構體。全球數據以任何其他名稱。所以現在我們下降到你的代碼基數的幾分之一。有些事情很少發生(每一行代碼一次),你可以負責描述和討論並確定它是否值得。 – Yakk

0

我不知道我對你的答案,但如果我有我的對象生存糾正我覺得這是便攜式的,安全的,並增加了零開銷或複雜性:

#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <iterator> 


// @pre f must be an r-value reference - i.e. a temporary 
template<class F> 
auto resist_copies(F &&f) { 
    return std::reference_wrapper<F>(f); 
}; 

void removeIntervals(std::vector<double> &values, const std::vector<std::pair<int, int>> &intervals) { 
    values.resize(distance(
      begin(values), 
      std::remove_if(begin(values), end(values), 
          resist_copies([i = 0U, it = cbegin(intervals), end = cend(intervals)](const auto&) mutable 
    { 
     return it != end && ++i > it->first && (i <= it->second || (++it, true)); 
    })))); 
} 


int main(int argc, char **args) { 
    // Intervals of indices I have to remove from values 
    std::vector<std::pair<int, int>> intervals = {{1, 3}, 
                {7, 9}, 
                {13, 13}}; 

    // Vector of arbitrary values. 
    std::vector<double> values = {4.2, 6.4, 2.3, 3.4, 9.1, 2.3, 0.6, 1.2, 0.3, 0.4, 6.4, 3.6, 1.4, 2.5, 7.5}; 
    removeIntervals(values, intervals); 
    // intervals should contain 4.2,9.1,2.3,0.6,6.4,3.6,1.4,7.5 

    std: 
    copy(values.begin(), values.end(), std::ostream_iterator<double>(std::cout, ", ")); 
    std::cout << '\n'; 
}