將const std :: string&作爲參數傳遞的日子已經過去了嗎?

本文翻譯自:Are the days of passing const std::string & as a parameter over?

I heard a recent talk by Herb Sutter who suggested that the reasons to pass std::vector and std::string by const & are largely gone. 我聽過Herb Sutter最近的一次演講,他提出通過const &傳遞std::vectorstd::string的原因已基本消失。 He suggested that writing a function such as the following is now preferable: 他建議現在編寫諸如以下的函數是可取的:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

I understand that the return_val will be an rvalue at the point the function returns and can therefore be returned using move semantics, which are very cheap. 我知道return_val在函數返回時將是一個右值,因此可以使用移動語義返回,這非常便宜。 However, inval is still much larger than the size of a reference (which is usually implemented as a pointer). 但是, inval仍然比引用(通常實現爲指針)的大小大得多。 This is because a std::string has various components including a pointer into the heap and a member char[] for short string optimization. 這是因爲std::string具有各種組件,包括指向堆的指針和用於短字符串優化的成員char[] So it seems to me that passing by reference is still a good idea. 因此在我看來,通過引用傳遞仍然是一個好主意。

Can anyone explain why Herb might have said this? 誰能解釋爲什麼Herb可能會這麼說?


#1樓

參考:https://stackoom.com/question/gvdl/將const-std-string-作爲參數傳遞的日子已經過去了嗎


#2樓

std::string is not Plain Old Data(POD) , and its raw size is not the most relevant thing ever. std::string不是Plain Old Data(POD) ,並且其原始大小不是最重要的東西。 For example, if you pass in a string which is above the length of SSO and allocated on the heap, I would expect the copy constructor to not copy the SSO storage. 例如,如果傳入的字符串大於SSO的長度並在堆上分配,則我希望複製構造函數不會複製SSO存儲。

The reason this is recommended is because inval is constructed from the argument expression, and thus is always moved or copied as appropriate- there is no performance loss, assuming that you need ownership of the argument. 推薦這樣做的原因是因爲inval是根據參數表達式構造的,因此始終會適當地移動或複製-假設您需要參數的所有權,則不會造成性能損失。 If you don't, a const reference could still be the better way to go. 如果您不這樣做,則const引用仍然可能是更好的方法。


#3樓

Unless you actually need a copy it's still reasonable to take const & . 除非您實際上需要一個副本,否則採用const &仍然是合理的。 For example: 例如:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

If you change this to take the string by value then you'll end up moving or copying the parameter, and there's no need for that. 如果將其更改爲按值獲取字符串,則最終將移動或複製參數,而無需這樣做。 Not only is copy/move likely more expensive, but it also introduces a new potential failure; 複製/移動不僅可能更昂貴,而且還帶來了新的潛在故障。 the copy/move could throw an exception (eg, allocation during copy could fail) whereas taking a reference to an existing value can't. 複製/移動可能會引發異常(例如,複製期間的分配可能會失敗),而引用現有值則不會。

If you do need a copy then passing and returning by value is usually (always?) the best option. 如果確實需要副本,那麼按值傳遞和返回通常是(總是?)最好的選擇。 In fact I generally wouldn't worry about it in C++03 unless you find that extra copies actually causes a performance problem. 實際上,除非您發現多餘的副本實際上會導致性能問題,否則我通常不會在C ++ 03中擔心它。 Copy elision seems pretty reliable on modern compilers. 在現代編譯器上,複製省略似乎非常可靠。 I think people's skepticism and insistence that you have to check your table of compiler support for RVO is mostly obsolete nowadays. 我認爲,如今人們大多已經過時了,人們一直懷疑您必須檢查編譯器對RVO的支持表。


In short, C++11 doesn't really change anything in this regard except for people that didn't trust copy elision. 簡而言之,C ++ 11在這方面並沒有真正改變任何東西,除了不信任複製省略的人。


#4樓

This highly depends on the compiler's implementation. 這在很大程度上取決於編譯器的實現。

However, it also depends on what you use. 但是,這也取決於您使用什麼。

Lets consider next functions : 讓我們考慮下一個功能:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

These functions are implemented in a separate compilation unit in order to avoid inlining. 爲了避免內聯,這些功能在單獨的編譯單元中實現。 Then : 然後 :
1. If you pass a literal to these two functions, you will not see much difference in performances. 1.如果將文字傳遞給這兩個函數,則性能不會有太大差異。 In both cases, a string object has to be created 在這兩種情況下,都必須創建一個字符串對象
2. If you pass another std::string object, foo2 will outperform foo1 , because foo1 will do a deep copy. 2.如果傳遞另一個std :: string對象, foo2性能將優於foo1 ,因爲foo1會進行深度複製。

On my PC, using g++ 4.6.1, I got these results : 在我的PC上,使用g ++ 4.6.1,我得到了以下結果:

  • variable by reference: 1000000000 iterations -> time elapsed: 2.25912 sec 參考變量:1000000000次迭代->經過的時間:2.25912秒
  • variable by value: 1000000000 iterations -> time elapsed: 27.2259 sec 按值可變:1000000000次迭代->經過的時間:27.2259秒
  • literal by reference: 100000000 iterations -> time elapsed: 9.10319 sec 參考值字面值:100000000次迭代->經過的時間:9.10319秒
  • literal by value: 100000000 iterations -> time elapsed: 8.62659 sec 按值的原義值:100000000次迭代->經過的時間:8.62659秒

#5樓

I've copy/pasted the answer from this question here, and changed the names and spelling to fit this question. 我已經在此處複製/粘貼了該問題的答案,並更改了名稱和拼寫以適合該問題。

Here is code to measure what is being asked: 以下是用於衡量要求的代碼:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

For me this outputs: 對我來說,輸出:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

The table below summarizes my results (using clang -std=c++11). 下表總結了我的結果(使用clang -std = c ++ 11)。 The first number is the number of copy constructions and the second number is the number of move constructions: 第一個數字是複製構造的數量,第二個數字是移動構造的數量:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

The pass-by-value solution requires only one overload but costs an extra move construction when passing lvalues and xvalues. 按值傳遞解決方案僅需要一個重載,但在傳遞左值和x值時會花費額外的移動構造。 This may or may not be acceptable for any given situation. 對於任何給定的情況,這可能是可接受的,也可能是不可接受的。 Both solutions have advantages and disadvantages. 兩種解決方案都有優點和缺點。


#6樓

The reason Herb said what he said is because of cases like this. 赫伯之所以說他的話,是因爲這樣的情況。

Let's say I have function A which calls function B , which calls function C . 假設我有一個函數A調用函數B ,該函數調用函數C And A passes a string through B and into C . A將字符串通過B傳遞到C A does not know or care about C ; A不知道或不在乎C ; all A knows about is B . A知道B That is, C is an implementation detail of B . 也就是說, CB的實現細節。

Let's say that A is defined as follows: 假設A定義如下:

void A()
{
  B("value");
}

If B and C take the string by const& , then it looks something like this: 如果B和C通過const&接受字符串,則它看起來像這樣:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

All well and good. 一切都很好。 You're just passing pointers around, no copying, no moving, everyone's happy. 您只是在傳遞指針,沒有複製,沒有移動,每個人都很高興。 C takes a const& because it doesn't store the string. C採用const&因爲它不存儲字符串。 It simply uses it. 它只是使用它。

Now, I want to make one simple change: C needs to store the string somewhere. 現在,我想做一個簡單的更改: C需要將字符串存儲在某個地方。

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

Hello, copy constructor and potential memory allocation (ignore the Short String Optimization (SSO) ). 您好,請複製構造函數和潛在的內存分配(忽略短字符串優化(SSO) )。 C++11's move semantics are supposed to make it possible to remove needless copy-constructing, right? C ++ 11的移動語義應該可以消除不必要的複製構造,對嗎? And A passes a temporary; A經過一個臨時的; there's no reason why C should have to copy the data. 沒有理由爲什麼C應該必須複製數據。 It should just abscond with what was given to it. 它應該潛移默化地給予它。

Except it can't. 除了它不能。 Because it takes a const& . 因爲它需要一個const&

If I change C to take its parameter by value, that just causes B to do the copy into that parameter; 如果我將C更改爲按值獲取其參數,那隻會導致B將其複製到該參數中; I gain nothing. 我什麼都沒有。

So if I had just passed str by value through all of the functions, relying on std::move to shuffle the data around, we wouldn't have this problem. 因此,如果我剛剛通過std::move通過所有函數按值傳遞str ,就可以隨機std::move數據,那麼我們就不會遇到這個問題。 If someone wants to hold on to it, they can. 如果有人想堅持下去,他們可以。 If they don't, oh well. 如果他們不這樣做,那就好。

Is it more expensive? 更貴嗎? Yes; 是; moving into a value is more expensive than using references. 轉化爲價值比使用引用要昂貴得多。 Is it less expensive than the copy? 它比副本便宜嗎? Not for small strings with SSO. 不適用於帶有SSO的小字符串。 Is it worth doing? 這值得嗎?

It depends on your use case. 這取決於您的用例。 How much do you hate memory allocations? 您討厭多少內存分配?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章