【问题标题】:C++ Difference between std::ref(T) and T&?C++ std::ref(T) 和 T& 之间的区别?
【发布时间】:2016-01-19 08:51:12
【问题描述】:

我对此计划有一些疑问:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
    auto r=ref(x);
    cout<<boolalpha;
    cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
    int x=5;
    foo (x);
    return 0;
}

输出是:

false

我想知道,如果std::ref 不返回对象的引用,那它有什么作用呢?基本上,两者有什么区别:

T x;
auto r = ref(x);

T x;
T &y = x;

另外,我想知道为什么会存在这种差异?当我们有引用(即T&amp;)时,为什么我们需要std::refstd::reference_wrapper

【问题讨论】:

标签: c++ reference ref


【解决方案1】:

ref 构造了一个适当的reference_wrapper 类型的对象来保存对对象的引用。这意味着当您申请时:

auto r = ref(x);

这会返回一个reference_wrapper 而不是对x 的直接引用(即T&amp;)。这个reference_wrapper(即r)持有T&amp;

reference_wrapper 在您想模拟可以复制的对象的 reference 时非常有用(它既是 copy-constructible 也是 copy-assignable)。

在 C++ 中,一旦您创建了对对象(例如 x)的引用(例如 y),那么 yx 将共享相同的基地址。此外,y 不能引用任何其他对象。你也不能创建一个引用数组,即这样的代码会抛出一个错误:

#include <iostream>
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    int& arr[] {x,y,z};    // error: declaration of 'arr' as array of references
    return 0;
}

不过这是合法的:

#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a: arr)
        cout << a << " ";
    return 0;
}
/* OUTPUT:
5 7 8
*/

说说你对cout &lt;&lt; is_same&lt;T&amp;,decltype(r)&gt;::value;的问题,解决方法是:

cout << is_same<T&,decltype(r.get())>::value;  // will yield true

让我给你看一个程序:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;

int main()
{
    cout << boolalpha;
    int x=5, y=7;
    reference_wrapper<int> r=x;   // or auto r = ref(x);
    cout << is_same<int&, decltype(r.get())>::value << "\n";
    cout << (&x==&r.get()) << "\n";
    r=y;
    cout << (&y==&r.get()) << "\n";
    r.get()=70;
    cout << y;
    return 0;
}
/* Ouput:
true
true
true
70
*/

看这里我们了解三件事:

  1. reference_wrapper 对象(此处为 r)可用于创建 引用数组,这在 T&amp; 中是不可能的。

  2. r 实际上就像一个真正的引用(查看r.get()=70 如何更改y 的值)。

  3. rT&amp; 不同,但 r.get() 不同。这意味着r 拥有T&amp;,即顾名思义是一个引用的包装 T&amp;

我希望这个答案足以解释你的疑惑。

【讨论】:

  • 1:不,reference_wrapper 可以重新分配,但它不能“保持对多个对象的引用”。 2/3:关于.get() 合适位置的公平点 - 不带后缀的r 可以T&amp; 的情况下与T&amp; 一样使用转换 operator 可以明确调用 - 因此在许多情况下无需调用 .get(),包括您的代码中的几个(由于缺少空格而难以阅读)。
  • @underscore_d reference_wrapper 可以保存一组引用,如果您不确定,那么您可以自己尝试。加上.get() 用于更改reference_wrapper 所持有的对象的值,即r=70 是非法的,因此您必须使用r.get()=70。自己试试!!!!!!
  • 并非如此。首先,让我们使用准确的措辞。您正在显示一个 reference_wrapper 持有 对数组的引用 - 而不是一个 reference_wrapper 本身 包含“对多个对象的引用”。包装器只保存一个引用。其次,我可以很好地获得对数组的本机引用 - 你确定你没有忘记int a[4]{1, 2, 3, 4}; int (&amp;b)[4] = a; 周围的(括号)吗? reference_wrapper 在这里并不特别,因为本机 T&amp; 确实 工作。
  • @AnkitAcharya 是的 :-) 但确切地说,有效结果,本身只指一个对象。无论如何,您当然是对的,与普通 ref 不同,wrapper 可以放入容器中。这很方便,但我认为人们将其误解为比实际更先进。如果我想要一个'refs'数组,我通常会跳过vector&lt;Item *&gt; 的中间人,这就是wrapper 归结为......并希望反指针纯粹主义者找不到我。它的令人信服的用例是不同的而且更复杂。
  • "当您想模拟可复制对象的引用时,reference_wrapper 非常有用。"但这不会违背参考的目的吗?你说的是实际情况。这就是参考。可以复制的引用似乎毫无意义,因为如果您想复制它,那么您首先不需要引用,您应该只复制原始对象。解决一个本来就不需要存在的问题似乎是不必要的复杂层。
【解决方案2】:

std::reference_wrapper 被标准设施识别为能够在按值传递的上下文中通过引用传递对象。

例如,std::bind 可以将std::ref() 接收到某物,按值传输它,然后将其解包回引用。

void print(int i) {
    std::cout << i << '\n';
}

int main() {
    int i = 10;

    auto f1 = std::bind(print, i);
    auto f2 = std::bind(print, std::ref(i));

    i = 20;

    f1();
    f2();
}

这个 sn-p 输出:

10
20

i 的值在初始化时已存储(按值获取)到f1,但f2 按值保留了std::reference_wrapper,因此其行为就像接收了@ 987654330@.

【讨论】:

  • @CppNITR 确定!给我一点时间来组装一个小演示:)
  • T&&ref(T)有什么区别
  • @CppNITR std::ref(T) 返回一个std::reference_wrapper。它只不过是一个包装的指针,但被库识别为“嘿,我应该是一个参考!一旦你完成传递我,请把我变回一个”。
【解决方案3】:

引用(T&amp;T&amp;&amp;)是 C++ 语言中的特殊元素。它允许通过引用操作对象,并且在语言中有特殊的用例。例如,您不能创建标准容器来保存引用:vector&lt;T&amp;&gt; 格式错误并生成编译错误。

另一方面,std::reference_wrapper 是一个能够保存引用的 C++ 对象。因此,您可以在标准容器中使用它。

std::ref 是一个标准函数,它在其参数上返回 std::reference_wrapper。同样的想法,std::crefstd::reference_wrapper 返回到 const 引用。

std::reference_wrapper 的一个有趣属性是它有一个operator T&amp; () const noexcept;。这意味着即使它是一个真正的对象,它也可以自动转换为它所持有的引用。所以:

  • 因为它是一个可复制的可赋值对象,所以它可以在容器中或其他不允许引用的情况下使用
  • 感谢它的operator T&amp; () const noexcept;,它可以在任何可以使用引用的地方使用,因为它会自动转换为它。

【讨论】:

  • 赞成主要是因为提到了operator T&amp; (),其他2个答案没有提到。
猜你喜欢
  • 2021-03-30
  • 2021-07-10
  • 2023-04-11
  • 2021-06-21
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多