沉寂了很久,從今天開始重操舊業,做點題啥的,一方面是比較功利性的,另一方面……好吧確實是比較功利性的,沒什麼特殊的原因。
今天遇到了一個很有意思的問題:string超出內存限制。
題目其實很簡單,也沒什麼太多的坑;雖然說測試用例裏的字符串確實很大,但理論上來說是不應該出現這個問題的,並且我在本地測試也沒有問題(本地是MinGW的g++)。
後來發現問題出在這裏,比如我要實現一個字符串反轉(以下是示意,實際使用大概率還是使用STL提供的reverse方法):
string reverse(string S)
{
int length = S.length();
string result;
for (int i = length - 1; i >= 0; --i)
{
result = S[i] + result; // here
}
return result;
}
就是這個operator+導致的超出內存限制。爲什麼呢?我們來看看C++ standard裏是怎麼寫的:
// operator+
template<class charT, class traits, class Allocator>
basic_string<charT,traits,Allocator>
operator+(const basic_string<charT,traits,Allocator>& lhs,
const basic_string<charT,traits,Allocator>& rhs);
Returns: basic_string<charT,traits,Allocator>(lhs).append(rhs)
template<class charT, class traits, class Allocator>
basic_string<charT,traits,Allocator>
operator+(basic_string<charT,traits,Allocator>&& lhs,
const basic_string<charT,traits,Allocator>& rhs);
Returns: std::move(lhs.append(rhs))
// operator+=
basic_string&
operator+=(const basic_string& str);
Effects: Calls append(str).
Returns: *this.
basic_string&
operator+=(charT c);
Effects: Calls push_back(c);
Returns: *this.
各看一個例子就已經可以說明問題了,多放幾個是因爲有助於後續的說明。從函數的signature中可以看出,operator+=返回的是引用,而operator+返回的是值,使用operator+在C++11之前很有可能會導致大量的拷貝行爲,需要有額外的空間來存儲臨時創建的右值,在string很大的情況下,就有可能超出內存限制。
在C++11之後,引入了右值引用和move函數,避免了這個問題。從standard中也可以看到,有一個版本的重載就是調用的move函數(從上往下數第二個)。
C++11之前沒有右值引用這一說法,但有的編譯器會有返回值優化,遇到這種情況會進行優化,所以如果不開啓11的編譯選項,在某些編譯器上也會自動進行優化,直接導致了在部分編譯器上不會觸發轉移構造函數的奇怪行爲。如果不需要返回值優化RVO,在GCC上可以加上-fno-elide-constructors
來禁止返回值優化的行爲。
當然,這裏的重點不是右值引用的問題,如果需要進一步瞭解,有一篇文章寫得很好,值得一看:C++ rvalue references and move semantics for beginners。
這篇文章其實到這裏已經可以結束了,但之前只用過string的operator+和operator+=,不知道string類還有append和push_back兩個函數可以進行添加(這同時也坐實了string是容器這一事實,沒必要再爭論string算不算容器,機考能不能用這種問題了……)
一圖勝千言,這張圖就足以概括他們的主要區別了(來源是參考資料2):
事實上,從standard中也可以看到,operator+=其實就是調用的append
和push_back
;也可以從這裏看到運算符重載的好處。