对引用引用的函数的直观理解[重复]

Posted

技术标签:

【中文标题】对引用引用的函数的直观理解[重复]【英文标题】:Intuitive understanding of functions taking references of references [duplicate] 【发布时间】:2011-11-01 12:16:02 【问题描述】:

可能重复:What does T&& mean in C++11?

由于某种原因,这超出了我的直觉,我在互联网上找不到任何解释。 C++ 函数获取引用的引用是什么意思?例如:

void myFunction(int&& val);     //what does this mean?!

我理解通过引用传递的想法,所以

void addTwo(int& a)

    a += 2;


int main()

    int x = 5;
    addTwo(x);

    return 0;

有效,对我来说很直观。

【问题讨论】:

@Fred:我会投票让两者都保持打开状态,您提到的重复项的答案使用 Standardese 说话很重,而下面的 templatetypedef 的答案给出了对使用的简洁解释(但不是真正的定义)通俗地说。 @Matthieu:问题(以及答案)不能合并,因为它们是同一个问题吗?我们不应该保留两个相同的问题。 【参考方案1】:

这不是对引用的引用,而是一种称为rvalue reference 的新语言功能,它(非正式地)表示对内存中对象的引用,该对象在程序的其他地方没有被引用,并且可以被破坏性地修改。例如,一个函数的返回值可以被一个右值引用捕获,就像在表达式中引入的临时值一样。

右值引用可用于多种用途。从大多数 C++ 程序员的角度来看,它们可用于实现 move semantics,从而可以通过将旧对象的内容从旧对象“移动”到新对象中来初始化新对象。您可以使用它从 C++11 中的函数返回巨大的对象,而无需支付大量成本来复制对象,因为用于捕获返回值的对象可以使用移动构造函数进行初始化,只需从临时对象中窃取内部信息由 return 语句创建。

移动语义与复制语义正交,因此对象可以移动而不是可复制。例如,std::ofstreams 不可复制,但它们是可移动的,因此您可以使用移动行为从函数中返回 std::ofstreams。目前这在 C++03 中无法完成。例如,这段代码在 C++03 中是非法的,但在 C++11 中却完全没问题(并且受到鼓励!):

std::ifstream GetUserFile() 
    while (true) 
        std::cout << "Enter filename: ";
        std::string filename;
        std::getline(std::cin, filename);

        ifstream input(filename); // Note: No .c_str() either!
        if (input) return input;

        std::cout << "Sorry, I couldn't open that file." << std::endl;
    


std::ifstream file = GetUserFile(); // Okay, move stream out of the function.

直观地说,一个接受右值引用的函数是一个(可能)试图通过将旧对象的内容移动到新对象中来避免昂贵副本的函数。例如,您可以为类向量对象定义 move 构造函数,方法是让该构造函数接受右值引用。如果我们将向量表示为数组指针、数组容量和已用空间的三元组,我们可以如下实现其移动构造函数:

vector::vector(vector&& rhs) 
    /* Steal resources from rhs. */
    elems    = rhs.elems;
    size     = rhs.size;
    capacity = rhs.capacity;

    /* Destructively modify rhs to avoid having two objects sharing 
     * an underlying array.
     */
    rhs.elems    = nullptr; // Note use of nullptr instead of NULL
    rhs.size     = 0;
    rhs.capacity = 0;

重要的是要注意,当我们在构造函数末尾清除 rhs 时,我们最终会将 rhs 置于这样的状态

    在其析构函数调用时不会导致崩溃(请注意,我们将其元素指针设置为 nullptr,因为释放 nullptr 是安全的),并且 仍然允许为对象分配一个新值。后一点很棘手,但重要的是要确保您仍然可以在某个时候为清除的对象赋予新值。这是因为有可能获得一个对象的右值引用,该对象在以后的程序中仍然可以被引用。

为了阐明 (2),右值引用的一个有趣用例是在对象之间显式移动值的能力。例如,考虑swap 的这个惯用实现:

template <typename T> void swap(T& lhs, T& rhs) 
    T temp = lhs;
    lhs = rhs;
    rhs = temp;

此代码是合法的,但有点不寻常。特别是,它最终制作了三个副本 - 首先将temp 设置为lhs 的副本,将lhs 设置为rhs 的副本,然后将rhs 设置为rhs 的副本temp。但是我们真的不想在这里复制任何东西。相反,我们只想打乱这些值。因此,在 C++11 中,您将能够使用 std::move 函数显式获取对对象的右值引用:

template <typename T> void swap(T& lhs, T& rhs) 
    T temp = std::move(lhs);
    lhs = std::move(rhs);
    rhs = std::move(temp);

现在,根本没有复制。我们将lhs的内容移动到temp,然后将rhs的内容移动到lhs,然后将temp的内容移动到rhs。在这样做的过程中,我们暂时将lhsrhs 置于“清空”状态,然后再将新值放入其中。重要的是,在编写代码以将内容移出对象时,我们要让对象处于某种格式良好的状态,这样代码才能正常工作。

【讨论】:

您能提供一个简单的代码示例来演示它的使用吗? @Daniel- 是的,刚刚做到了!如果还有什么我可以澄清的,请告诉我! 只要确保在被“破坏性修改”之后类必须仍然有效。其中重要的部分是:它应该仍然是可破坏的,并且是可分配的。 @Mooing Duck- 感谢您指出这一点!我刚刚更新了答案以解释这一点以及为什么有必要。 右值引用使用的精彩解释!【参考方案2】:

这不是对引用的引用。这是 C++0x 中为所谓的Rvalue references 引入的一种新语法。

【讨论】:

以上是关于对引用引用的函数的直观理解[重复]的主要内容,如果未能解决你的问题,请参考以下文章

在 JavaScript 原型函数中保留对“this”的引用 [重复]

c ++对静态库函数的“未定义引用”[重复]

对带有数组的构造函数的未定义引用[重复]

使用类标头对函数的未定义引用[重复]

使用 QT 时对构造函数的未定义引用 [重复]

链接 [重复] 时 gcc 找不到对函数 DoIt() 的引用