【问题标题】:What is the purpose of rvalue reference to an array in C++11?在 C++11 中对数组进行右值引用的目的是什么?
【发布时间】:2014-03-21 14:56:44
【问题描述】:

在 C++03 和 C++11 中,数组不能通过函数的值返回(只能通过引用/常量引用)(因为我们不能将一个数组直接分配给另一个数组):

const size_t N = 10;
using Element = int;
using Array = Element[N];

Array array;

// does not compile
// Array GetArray()
// {
//     return array;
// }

Array& GetArrayRef()
{
    return array;
}

在 C++ 中引入了一种新的引用类型 - 右值引用。它也可以与数组一起使用:

void TakeArray(Array&& value)
{
}

// ...

TakeArray(std::forward<Array>(array));
TakeArray(std::forward<Array>(GetArrayRef()));

这种引用(对数组的右值引用)的目的是什么?可以在任何实际代码中使用还是只是C++11标准的缺陷?

【问题讨论】:

  • 您的第一个假设并不完全正确。使用 c++11 可以定义一个 std::array,它可以按值返回。
  • @MatthiasB std::array 不是数组,它是std 容器。
  • @Constructor 我认为int array[] 不是一个数组,而是C 兼容性的遗物。 std::array 更像是一个真正的数组(很少违反最小惊喜原则)
  • 这有实际的可能用途(参见@Yakk)。但是,一些没用的东西仍然是合法的,比如类的右值引用成员。这不是缺陷,只是没有人认为有任何理由将其定为非法。
  • @SamCristall 可能你是对的。 :-) 但是它们仍然符合标准,所以...

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


【解决方案1】:

右值引用是程序员或编译器的一个承诺,即所引用的数据即将被处理1,并且如果它使读者以合理的方式更改它是可以接受的阅读速度更快(除其他外)。

如果您使用上述承诺从vector 数组复制,您可以清楚地move 包含vectors 以获得可能的巨大性能提升。

所以不,这不是缺陷。

std::vector<int> data[1000];
void populate_data() {
  for( auto& v : data )
    v.resize(1000);
}
template<std::size_t N>
std::vector< std::vector<int> > get_data( std::vector<int>(&&arr)[N] ) {
  std::vector< std::vector<int> > retval;
  retval.reserve(N);
  for( auto& v : arr )
    retval.emplace_back( std::move(v) );
  return retval;
}
template<std::size_t N>
std::vector< std::vector<int> > get_data( std::vector<int>(const&arr)[N] ) {
  std::vector< std::vector<int> > retval;
  retval.reserve(N);
  for( auto const& v : arr )
    retval.emplace_back( v );
  return retval;
}
int main() {
  populate_data();
  auto d = get_data(data); // copies the sub-vectors
  auto d2 = get_data( std::move(data) ); // moves the sub-vectors
}

在这种情况下,事情有点做作,但通常数据的来源可能是moved,也可能不是。如果整个容器是来自(右值引用)的moved,则右值覆盖会移动子数据,否则会进行复制。

再多做一些工作,我们就可以编写上面的代码,这样这一切都可以在一个方法中完成。因此,如果我们有一个右值容器(又名拥有范围),并且容器本身不适合直接从中移动,我们 move 内容。但这只是高级元编程,对答案并不重要。


1 编译器在涉及对象“不可能”在使用后引用的受限情况下执行此操作,因为它是匿名临时对象,或者因为它在某些情况下被用于返回值的功能。 (这实际上并非不可能,理论上这个 C++11 特性可能会破坏 C++11 之前的代码,但它非常接近)。程序员通过右值转换来做到这一点。它将被“立即处理”的确切含义取决于上下文,但一般规则是右值引用的使用者可以将对象置于仍然可以被销毁并与之交互的任何“有效”状态。基于右值的swap 的存在意味着将其置于只能被销毁的状态是不安全的。

【讨论】:

  • 你能举一个代码例子来说明你的话吗?
  • 非常感谢!有意思,我会考虑如何在一个函数中实现。
  • @Constructor 它变得混乱。您的函数签名变为template&lt;typename C&gt;void get_data( C&amp;&amp; c )。您需要帮助函数来回答诸如“这是一个拥有的容器”、“如果有有效的方法,请给我这个视图的大小,否则为 0”和“move_if&lt;bool&gt;”(或make_move_iterator_if&lt;bool&gt;)然后创建一个constexpr bool move_from = std::is_rvalue_ref&lt; C&amp;&amp; &gt;::value 来控制移动与否。后期概念也变得更容易了。
  • 是的,我也认为它可以以某种方式实现。
  • 在右值重载的 foreach 循环中,auto&amp; v 不应该是 auto&amp;&amp; v,还是我误解了右值的工作原理?
【解决方案2】:

不能拥有临时数组并不完全正确。你可以

void f(const std::string(&&x)[2]) {
   std::cout << "rvalue" << std::endl;
}

void f(const std::string(&x)[2]) {
   std::cout << "lvalue" << std::endl;
}

template<typename T>
using id = T;

int main() {
  f(id<std::string[]>{"Hello", "Folks"});
  f(id<std::string const[]>{"Hello", "Folks"});

  std::string array[] = { "Hello", "Folks" };
  std::string const arrayConst[] = { "Hello", "Folks" };
  f(array);
  f(arrayConst);
}

从 C++14 开始,you are also allowed 将大括号初始化列表直接传递给右值重载

f({ "Hello", "Folks" });

【讨论】:

  • 太棒了!但这可以在不使用别名模板的情况下完成:f((std::string[]){"Hello", "Folks"});。你想在第二对f 函数调用中显示什么?
  • @Constructor 你可以区分左值和右值。第二对调用将调用第二个f,因为它们是左值。与第一对电话相反。如果没有别名模板或 typedef,您将无法做到这一点。
  • 哦,我现在明白了,你带来这段代码是为了展示为右值和左值数组调用不同的重载函数。关于f((std::string[]){"Hello", "Folks"});:为什么这段代码在clang和gcc下都编译?它是编译器错误吗?
  • @Constructor 是 C99 复合文字,不是有效的 C++ 语法。它甚至有不同的语义,因为数组的元素在整个调用范围块中都有生命周期(除非编译器选择以不同于 C99 描述的方式实现 C++ 扩展)
  • 谢谢。我忘了-pedantic-errors 标志。 :-)
【解决方案3】:

右值引用有两件事:

  • 这是一个参考,包含所有内容
  • 向编译器承诺,所引用的对象是未命名的(临时的)或可以这样对待

在 C++ 中,通过引用传递数组可以确保其长度,这与 C 的 void func(int a[5]) 相比已经是一个改进,其中传递长度为 2 的数组是完全可以接受的(尽管好的编译器会发出警告) .

然而,对数组的右值引用的好处并不那么直接:

  • 一个未命名的对象不能被别名;因此,r 值引用必然没有别名,好像已指定 restrict
  • 警告调用者数组的元素可以移动

因此,即使使用数组,也可以进行优化。

【讨论】:

  • 如何获取未命名数组?
  • struct A { int array[5]; }; + A func(); + call(func().array) 例如。在后一个表达式中,func 返回的值未命名,因此 func().array 也未命名。
【解决方案4】:

这几乎不是一个缺陷,它引入了将对象移动到目标的令人兴奋的能力。这意味着目标句柄被交换为即将超出范围的源。

【讨论】:

  • 您能否举一个代码示例来演示您在将右值引用应用于数组时所用的话?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 2018-08-06
  • 1970-01-01
  • 2012-02-10
  • 1970-01-01
  • 2012-01-26
相关资源
最近更新 更多