2

请理解我仍在学习 C++11 的移动和右值语义的概念。我的问题是遗留代码是否可以通过简单地使用 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::vectora 有移动构造函数。这段代码可以避免任何不必要的副本吗?或者,我应该在上面的代码中使用&&还是std::forward在上面的代码中使用?

我试图调试内部 STL 代码,但很难说服。

注意:我的目标是尽量减少这些函数中不必要的副本。我知道我可以使用 new/pointers/references,但这需要解决内存泄漏问题。所以,我想尽可能多地使用值。

4

2 回答 2

5

对于 Q1,即使在 C++03 中也很可能没有副本,因为副本已被“命名返回值优化”(NRVO)删除。

对于 Q2,即使在 C++03 中也很可能没有副本,因为复制省略会删除它。

对于 Q3,即使在 C++11 中,您也确实有副本,因为您需要标记f1f2可移动以便实际移动它们:

vector<Frequencies> fs = { std::move(f1), std::move(f2) };

由于您提出了多个问题,我想我将省略进一步的解释,查找 NRVO、复制省略和std::move需要的位置,询问您是否还有其他问题。

但是,在某些情况下您可以获得免费移动,例如,如果有一个可以移动的临时对象:

vector<Frequencies> fs = { buildFrequenciesTable("apple"),
                           buildFrequenciesTable("bananas") };

以上将检测从buildFrequenciesTable()作为临时返回的两个向量,因此它们将被移动到fs.

于 2013-10-21T18:13:41.853 回答
1

从函数 ( Q1) 返回向量将尽可能使用移动语义,而无需修改代码。同样,从返回的临时 ( Q2) 中初始化向量将使用移动语义;返回值是一个rvalue,因此可以从中移动。在实践中,两个移动(或历史上的副本)都应该被省略,以便函数初始化f1并且f2直接没有移动或复制。

将它们放入向量 ( Q3) 确实需要复制:变量是值,不能隐式移动。因此,您必须使用std::move或重组代码以避免这些副本。

于 2013-10-21T18:17:37.980 回答