string超出內存限制:C++ string的operator+=、operator+、append與push_back

沉寂了很久,從今天開始重操舊業,做點題啥的,一方面是比較功利性的,另一方面……好吧確實是比較功利性的,沒什麼特殊的原因。

今天遇到了一個很有意思的問題: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+=其實就是調用的appendpush_back;也可以從這裏看到運算符重載的好處。

參考資料

  1. 爲什麼超出內存限制?
  2. std::string::append vs std::string::push_back() vs Operator += in C++
  3. string::op+= / online c++ standard draft
  4. C++ rvalue references and move semantics for beginners
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章