命名的右值引用

Named rvalue references

本文关键字:引用      更新时间:2023-10-16

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

#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. 对于使用自定义类的相同方法,我是否需要定义move构造函数

这个代码做的正确吗?

是的。C++11添加CCD_ 1正是出于这个原因。

有更好的方法吗?

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

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

这要归功于C++11引用折叠规则和std::forward。模板类型Tstd::vector::push_back(T&&)0是左值的情况下变成vector<int>&,而在v是右值的情况中变成vector<int>&&。参考折叠规则意味着vector<int>& &&变成vector<int>&,而vector<int>&& &&变成vector<int>&&。这正是你想要的,调用push_back的版本,在左值的情况下进行复制,但在右值的情况中进行移动。

一个缺点是,当你出错时,这有时会导致有趣的诊断。(这里的"有趣"是指g++或clang++中数百行难以理解的诊断文本)。另一个缺点是模板可能导致"转换器失控"的情况。

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

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

对我来说,这有点太难记住了。我不知道这是一个好的做法还是一个坏的做法,但我已经开始使用Foo(const Foo&)=default,对其他五个函数的规则也有类似的声明。在许多情况下,我还会将构造函数限定为explicit,以避免"转换器失控"的问题。

  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,你就需要这些类是可移动构造的,以避免复制。不过,如果你能得到默认的定义,你不一定需要自己定义移动构造函数。