为什么返回右值引用是错误的

why is returning rvalue reference wrong

本文关键字:错误 引用 返回 为什么      更新时间:2023-10-16

我想知道有一个r-引用返回类型出了什么问题:

vector<T>&& id2(vector<T>&& v) {  return std::move(v);}

,如下面的代码所示。使a = id2(a)集为空。另一个版本id返回正常类型为OK。

我听说这与自移动赋值有关。但我对移动语义只有基本的了解,而且我不理解从标准中引用的答案中的隐晦语言。有人能用常识/外行的语言解释一下吗?

如果我取一个临时的a,收获它的内容,并将内容作为临时分配回a,为什么a要被清除或未定义?

#include <vector>
#include <iostream>
using namespace std;
template <class T>
vector<T> id(vector<T>&& v) {  return std::move(v); }
template <class T>
vector<T>&& id2(vector<T>&& v) {  return std::move(v);}
int main() {
  vector<int> a;
  a.resize(3,1);
  a = id(std::move(a));
  cout << a.size() << endl;  //output: 3
  a.resize(3,1);
  a = id2(std::move(a));
  cout << a.size() << endl;  //output: 0
}

谢谢,

—Update—

如果我正在阅读上述重复的帖子,下面引用的第二个例子是关于将r-ref传递给函数外部的局部变量,无论这是r-ref还是l-ref,这都是一个错误。我觉得这和我的例子不太一样。如果在我的例子中使用左引用而不是r-ref,它就会起作用。我遗漏了什么吗?

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();

—Update 2—

@pradhan。这个问题似乎可以归结为自移动分配。关于标准容器的自移动赋值行为的库选择,这是困扰我的地方。如果你用旧方式对std::vector进行自赋值,这是完全没问题的,但当你自移动赋值时,这是未定义行为。除非有一些固有的错误,它是更自然的(向后兼容),让标准容器允许自移动分配与自分配?

#include <vector>
#include <iostream>
int main() {
  std::vector<int>a{1,2,3};
  a = a;
  std::cout << a.size() << std::endl;  //output: 3
  a = std::move(a);
  std::cout << a.size() << std::endl;  //output: 0
}

您的函数id2什么也不做,代码相当于

a = std::move(a);

你链接的Howard Hinnant的回答解释说这本身是不允许的。通俗地说:该标准不要求支持自移动赋值。因此,移动赋值操作符的实现不需要执行自赋值测试,其结果将导致未定义行为。

如果你让a=std::move(a)合法,它将不做任何事情。问题是,这需要检查,这对于非自移动赋值情况是多余的,因此悲观所有这些赋值。

您违反了右值引用不别名的约定(这是调用库函数的要求)。问题中提到的Howard Hinnant的答案包含标准文本和自我移动的应用程序。下面是实际结果:

将move-assignment实现为:

T& T::operator=(T&& other)
{
    clear();
    swap(*this, other);
    return *this;
}

或更安全的异常变体:

T& T::operator=(T&& other)
{
    swap(*this, other);
    other.clear();
    return *this;
}

,只要遵守混叠约定,这也是安全的。

很明显,如果使用别名,这两种实现都会产生空对象。

顺便说一句,id2返回右值引用没有任何问题。问题是a = id2(a);使用右值引用,在同一表达式中别名另一个对象引用。

您的问题和Hinnant的回答中的链接都涉及标准对标准库实现的要求。a=std::move(a)本身没有什么问题。如果a的类类型为T,则a=std::move(a)的结果取决于T的实现。该标准允许(标准)库实现在给定右值引用参数时做出唯一的引用假设。当a具有类型T=vector<U>时,a=std::move(a)违反了唯一引用假设。由于您违反了图书馆合同,因此导致了UB。但是,您可以拥有自己的类,该类的move构造函数实现在wrt a=std::move(a)中具有完美定义的行为。标准没有说任何关于自移动赋值的事情。它只指定标准库实现者需要支持什么,以及他们可以选择不支持什么,如果他们愿意的话。