今天早上半夢半醒之間想到的技巧,不曉得是不是世所周知的技巧,至少這對最近比較少在用功的我來說,是新東西。

通常,我們要塞東西進 container,如讀取一個 CSV 檔,存進 vector< vector<string> > 裡,會這麼寫:

vector< vector<string> > csv_data;
ifstream ifs("data.csv");
while (ifs) {
    string line;
    getline(ifs, line);
    vector<string> tokens;
    // splits line string delimited by ",", and save to tokens.
    split(line, ",", tokens);
    csv_data.push_back(tokens);
}

問題出在 csv_data.push_back() 的那一行,tokens 會被「複製」一份進 csv_data,然後隨即被解構。這不禁讓我在想,若是 tokens 又肥又大,又不想要讓 csv_tokens 只裝 pointer to vector<string> 的話,那這個「複製」不就既耗時又耗記憶體?

例如,以上例來說,若這個 CSV 檔,每一行都各有 1000 個 entries,那 push_back() 總共要複製 1000 個 string,實在會是個效能殺手。

於是我就想到,其實 push_back() 那一行,我們可以這麼寫:

csv_data.push_back(vector<string>());          // push dummy entry first
csv_data.at(csv_data.size() - 1).swap(tokens); // swap with the real one

我們先 push_back 一個「空的」物件進去,因為是空的,所以「複製」成本極低。然後,再用 at() 得到 reference to 剛剛那個空物件,利用 swap()tokens 交換內容。交換之後,csv_data 的最後一個 entry,就有完整的資料,但 tokens 卻變成空物件。但因為 tokens 將被拋棄解構,所以沒有關係。

如此一來,我們就省去了 1000 個 string 的複製,只需要耗費空的 vector<string> 的複製成本,與 swap() 的成本就好。而通常來說,swap() 的成本極低,因此原本既耗時又耗記憶體的步驟,就被優化成既快速又省記憶體了。