【问题标题】:Named rvalue references命名右值引用
【发布时间】:2015-11-05 05:33:16
【问题描述】:

请原谅我对这个话题不够清楚。我正在尝试创建用于将大类插入向量的函数。在这个例子中,我使用整数向量作为大类。

#include <vector>
#include <iostream>
using namespace std;

vector<vector<int>> vectorOfVectors;

void fn(const vector<int> &v) {
    vectorOfVectors.push_back(v);

}
void fn(vector<int> &&v) {
    vectorOfVectors.push_back(std::move(v));
}

int main() {
    vector<int> a({1});
    const vector<int> b({2});
    fn(std::move(a));
    fn(b);
    cout<<b[0];
}

显然,我希望尽可能不进行复制。我的问题:

  1. 此代码是否正确?
  2. 有更好的方法吗?
  3. 对于使用自定义类的相同方法,是否需要定义移动构造函数?

【问题讨论】:

    标签: c++ c++11 rvalue-reference


    【解决方案1】:

    这段代码做对了吗?

    是的。正是出于这个原因,C++11 添加了std::vector::push_back(T&amp;&amp;)

    有更好的方法吗?

    您的fn(const vector&lt;int&gt; &amp;v)fn(vector&lt;int&gt; &amp;&amp;v) 都在做同样的事情,将参数v 推到vectorOfVectors 的末尾。代替您的两个 fn 函数,您可以使用一个使用完美转发的函数模板。

    template<typename T>
    void fn(T &&v) {
        vectorOfVectors.push_back(std::forward<T>(v));
    }
    

    这要归功于 C++11 reference collapsing rulesstd::forward。在v 是左值的情况下,模板类型T 变为vector&lt;int&gt;&amp;,但在v 是右值的情况下vector&lt;int&gt;&amp;&amp;。引用折叠规则意味着vector&lt;int&gt;&amp; &amp;&amp; 变为vector&lt;int&gt;&amp;vector&lt;int&gt;&amp;&amp; &amp;&amp; 变为vector&lt;int&gt;&amp;&amp;。这正是您想要的,调用 push_back 的版本,在左值的情况下执行复制,但在右值的情况下执行移动的版本。

    一个缺点是,当您遇到问题时,这有时会导致有趣的诊断。 (这里的“有趣”是指来自 g++ 或 clang++ 的数百行难以理解的诊断文本)。另一个缺点是模板可能会导致“转换器变得疯狂”。

    对于使用自定义类的相同方法,我是否需要定义移动构造函数?

    不一定。如果该类未声明用户定义的析构函数、复制构造函数、复制赋值运算符或移动赋值运算符,您将获得隐式声明的移动构造函数。如果类具有不可移动的数据成员或派生自无法移动或删除的类,则隐式声明的移动构造函数将被定义为已删除。

    对我来说,这有点太难记住了。我不知道这是一个好习惯还是坏习惯,但我已经开始使用Foo(const Foo&amp;)=default,对其他五个函数的规则进行了类似的声明。在许多情况下,我还会将构造函数限定为 explicit,以避免“转换器变得疯狂”的问题。

    【讨论】:

    • 您能否详细说明“在某些情况下是”部分?模板是如何工作的?
    • @user2308211 - 我将“在某些情况下,是的”更改为您的特定情况。概括一下,如果您有一个f(T&amp;)f(T&amp;&amp;),除了std::movef() 的移动版本中的两个之外,它们之间的复制和粘贴完全相同,您应该考虑模板化一个函数。
    • "在v 是右值的情况下,模板类型T 变为[...] vector&lt;int&gt;&amp;&amp;" - 不,在这种情况下T 只是vector&lt;int&gt;。转发参考版本还可以捕获所有内容(如果您有过载或需要检查 f(something) 是否格式正确,这一点很重要)。最后,如果有问题的类型移动起来很便宜(比如vector&lt;int&gt;),更简单的方法是按值获取参数,然后无条件移动:void f(vector&lt;int&gt; v) { vectorOfVectors.push_back(std::move(v)); }
    【解决方案2】:
    1. 确实避免了复制a
    2. 是的。使用push_back 意味着你不得不构造至少两个对象,而emplace_back 和完美转发可以做更少的工作。

      template<typename... Ts>
      auto fn(Ts &&...ts)
        -> decltype(vectorOfVectors.emplace_back(std::forward<Ts>(ts)...), void())
      {
          vectorOfVectors.emplace_back(std::forward<Ts>(ts)...);
      }
      

      http://melpon.org/wandbox/permlink/sT65g3sDxHI0ZZhZ

    3。只要您使用push_back,您就需要将类移动构造以避免复制。但是,如果您可以获得默认定义,则不一定需要自己定义移动构造函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-07
      • 2015-11-18
      • 2017-12-03
      • 2011-07-13
      • 1970-01-01
      • 2019-10-12
      • 1970-01-01
      相关资源
      最近更新 更多