请理解我还在学习C ++ 11的移动和rvalue语义的概念。我的问题是遗留代码是否可以通过简单地使用C ++ 11编译器和STL获得避免不必要的副本的免费午餐。
这是一个非常简单的例子。此代码为给定字符串构建一个简单的字符频率表。例如,“apple”应返回{('a', 1), ('e', 1), ('l', 1), ('p', 2)}
。正如您将看到的,我只是使用向量作为值。
typedef std::tuple<char, int> Frequency;
typedef std::vector<Frequency> Frequencies;
Frequencies buildFrequenciesTable(std::string w) {
char table['z' - 'a' + 1] = { 0, };
std::for_each(w.cbegin(), w.cend(), [&table](char c) {
++table[::tolower(c) - 'a'];
});
Frequencies freqs;
for (size_t i = 0; i < 'z' - 'a' + 1; ++i) {
if (table[i] != 0)
freqs.push_back(tuple<char, int>((char) ('a' + i), table[i]));
}
return freqs; // Q1: Is vector get copied?
}
int main() {
using namespace std;
Frequencies f1 = buildFrequenciesTable("apple"); // Q2: Copy?
Frequencies f2 = buildFrequenciesTable("banana");
vector<Frequencies> fs = { f1, f2 }; // Q3: Copy?
}
很明显,当将向量作为值返回时,C ++ 03会生成所有复制代码(使用复制构造函数和赋值运算符)。在C ++ 11中怎么样? std::vector
a有移动构造函数。这段代码可以避免任何unnessarry副本吗?或者,我应该在上面的代码中使用&&
还是std::forward
?
我尝试调试内部STL代码,但很难说服。
注意:我的目标是尽量减少这些功能中不必要的副本。我知道我可以使用new / pointers / references,但这需要解决内存泄漏问题。所以,我想尽可能多地使用数值。
答案 0 :(得分:5)
对于Q1,即使在C ++ 03中也很可能没有副本,因为“命名返回值优化”(NRVO)删除了副本。
对于Q2,即使在C ++ 03中也很可能没有副本,因为copy-elision会删除它。
对于Q3,即使在C ++ 11中,你做也需要将f1
和f2
标记为可移动,以便实际移动它们:
vector<Frequencies> fs = { std::move(f1), std::move(f2) };
由于您提出了多个问题,我想我会省略进一步的解释,查找NRVO,copy-elision以及需要std::move
的地方,询问您是否还有其他问题。
但是,有些情况下你可以获得自由移动,例如,如果有一个可以移动的临时移动:
vector<Frequencies> fs = { buildFrequenciesTable("apple"),
buildFrequenciesTable("bananas") };
上面会检测从buildFrequenciesTable()
返回的两个向量作为临时值,因此它们将被移动到fs
。
答案 1 :(得分:1)
从函数(Q1
)返回向量将尽可能使用移动语义,而无需修改代码。同样,从返回的临时(Q2
)初始化向量将使用移动语义;返回值是 rvalue ,因此可以移动。在实践中,应该省略两个移动(或历史上的副本),以便该函数直接初始化f1
和f2
而不移动或复制。
将它们放在向量(Q3
)中需要复制:变量是 lvalues ,不能隐式移动。因此,您必须使用std::move
或重组代码,以避免这些副本。