【问题标题】:C++11 rvalue and move: Can legacy code avoid copy?C++11 右值和移动:遗留代码可以避免复制吗?
【发布时间】:2013-10-30 08:07:16
【问题描述】:

请理解我仍在学习 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::vector a 有移动构造函数。这段代码可以避免任何不必要的副本吗?或者,我应该在上面的代码中使用&amp;&amp; 还是std::forward

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

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

【问题讨论】:

    标签: c++ c++11 move-semantics rvalue-reference call-by-value


    【解决方案1】:

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

    对于第二季度,即使在 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

    【讨论】:

    • 谢谢!那么,基本上,在 Q1 和 Q2 的情况下,C++11 的 r 值引用和移动语义没有什么好处?我的理解正确吗?
    • 我的目标实际上是尽量减少这些函数中不必要的副本。我知道我可以使用 new/pointers/references,但这需要疯狂的泄漏问题。所以,我想尽可能多地使用值。
    • 还有一个子问题(可能是愚蠢的问题):我可以进一步优化这段代码吗?比如在buildFrequenciesTable中返回Frequencies&amp;&amp;,或者声明Frequencies&amp;&amp; f1左右。特别是上面的f1f2不会被修改。
    • @Nullptr Q1 和 Q2 是最佳选择,因为 RVO/NRVO 和复制省略启动。虽然这可能无法保证,但根据我的经验,这是非常可靠的。如有疑问,您需要检查它。我建议不要摆弄Frequencies&amp;Frequencies&amp;&amp;,因为这会使代码变得不那么清晰(恕我直言),并且仅在特殊情况下才需要。但是,如果您关心性能,那么与复制省略和 RVO/NRVO 相关的知识(和经验!)是必不可少的,并且您应该真正研究一下。
    • ...除了您使用了向量的初始化列表构造函数,它不关心可移动性。 std::initializer_list&lt;T&gt; 和移动不要混合。
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2016-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-03
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      相关资源
      最近更新 更多