2018年2月6日 星期二

移動構造函數的必要性 並不像想像中的不重要

移動構造函數的必要性 並不像想像中的不重要

我知道移動構造函數非常重要可以節省很多成本,原本以為移動構造函數只是用到的時候才重要,今天發現一個問題解決了也意外發現,這個東西不能偷懶痾,他不像我想像中的那樣是主動使用才會遇到,某些情況下會在你不知道的時候自動呼叫。
今天再用vector的emplace_back()想了一些邏輯怎麼呼叫,原本的想法是需要移動構造函式他會自己用移動的方式移動進去省成本,沒有的話就是複製構造函式。
一開始沒住到我就傻傻的用地址的方式確認,這是我第一個遇到的問題,vector內容數值不能直接取址,沒遇到還真沒想到,直接取址會噴錯誤。(環境是 VS2017)
錯誤    C1001    編譯器發生內部錯誤。
弄了一下才想到 vector 正確的操作方式要用 iterator,取址要用 .data() 的方式。
回到正題來嘗試將一個數據 emplace_back() 進去
MyData a("a");
vector v;
v.emplace_back();
結果獲得複製進去的數據地址居然不一樣,跑去官方重看一次文件,發現我對emplace_back(); 的理解有誤,他並不是移動進去而是直接就地建造。
而原本上面的寫法會變成建構+移動。
而且上面的寫法也是不對的,那樣寫會跟直接push沒兩樣,要寫在同一行。
vector v;
v.emplace_back(MyData ("a"));
這個問題到這邊就解完了,只是後來我將它多推了幾次,又發現一個驚人的意外
#include <vector>
#include <string>
#include <iostream>

struct President
{
    std::string name;
    std::string country;
    int year;

    President(std::string && p_name, std::string && p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};

int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << "\n-------------\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);

    std::vector<President> reElections;
    std::cout << "\npush_back:\n";
    reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936));

    std::cout << "\nContents:\n";
    for (President const& president : elections) {
        std::cout << president.name << " was elected president of "
            << president.country << " in " << president.year << ".\n";
    }
    for (President const& president : reElections) {
        std::cout << president.name << " was re-elected president of "
            << president.country << " in " << president.year << ".\n";
    }
}
驚訝於結果居然不是只建構兩次!怎麼有一個移動。(這裡有一個隱性的雷,應該是呼叫複製建構子,屬於深層的記憶體搬動,只是這個範例沒寫複製建構子變成呼叫移動建構子了)
emplace_back:
I am being constructed.

-------------
I am being constructed.
I am being moved.
想了一下才發現原來是vector空間不足!另闢新天地然後把他們移動過去,嘗試多 emplace_back 幾次就可以發現了。
這就是問題所在了!如果你要想用STL容器,他就可能會在你沒注意到的時候使用了移動函式,所以不能省啊!大原則就是有用STL就得乖乖遵守5條規則,官方可能也是因此才有了 rule of five 這個規則吧。
就算足夠熟悉STL,寫的程式也確實都避開了,也不能保證後面的人或是客戶會知道你沒寫不小心寫了本來應該是移動結果變成複製的代碼。

沒有留言:

張貼留言