通过参考文献的参考直观地理解函数

发布于 2024-11-30 15:15:55 字数 505 浏览 1 评论 0原文

可能的重复:
T&&是什么?在 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;
}

对我来说很有效并且很直观。

Possible Duplicate:
What does T&& mean in C++11?

For some reason, this is eluding my intuition, and I cannot find any explanation on the internet. What does it mean for a C++ function to take a reference of a reference? For example:

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

I understand the idea of passing-by-reference, so

void addTwo(int& a)
{
    a += 2;
}

int main()
{
    int x = 5;
    addTwo(x);

    return 0;
}

works and is intuitive to me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夜清冷一曲。 2024-12-07 15:15:55

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

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

移动语义与复制语义正交,因此对象可以移动但不可复制。例如,std::ofstream 不可复制,但它们是可移动的,因此您可以使用移动行为从函数返回 std::ofstream。目前在 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.

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

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 置于这样一种状态,即

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

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

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

此代码是合法的,但有点不寻常。特别是,它最终会制作三个副本 - 首先将 temp 设置为 lhs 的副本,一旦将 lhs 设置为 lhs 的副本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中,然后移动内容temprhs 中。在此过程中,我们将 lhsrhs 暂时置于“清空”状态,然后再将新值放入其中。重要的是,在编写将内容移出对象的代码时,我们要使对象处于某种格式良好的状态,以便该代码正常工作。

This is not a reference of a reference, but rather a new language feature called an rvalue reference that represents (informally) a reference to an object in memory that isn't referenced elsewhere in the program and can be destructively modified. For example, the return value of a function can be captured by an rvalue reference, as can temporary values introduced into expressions.

Rvalue references can be used for a variety of purposes. From the perspective of most C++ programmers, they can be used to implement move semantics, whereby a new object can be initialized by "moving" the contents of an old object out of the old object and into a new object. You can use this to return huge objects from functions in C++11 without paying a huge cost to copy the object, since the object used to capture the return value can be initialized using the move constructor by just stealing the internals from the temporary object created by the return statement.

Move semantics are orthogonal to copy semantics, so objects can be movable without being copyable. For example, std::ofstreams are not copyable, but they will be movable, so you could return std::ofstreams from functions using the move behavior. This currently cannot be done in C++03. For example, this code is illegal in C++03 but perfectly fine (and encouraged!) in 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.

Intuitively, a function that takes an rvalue reference is a function that (probably) is trying to avoid an expensive copy by moving the contents of an old object into a new object. For example, you could define a move constructor for a vector-like object by having that constructor take in an rvalue reference. If we represent the vector as a triple of a pointer to an array, the capacity of the array, and the used space, we might implement its move constructor as follows:

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;
}

It's important to notice that when we clear out rhs at the end of the constructor that we end up putting rhs into such a state that

  1. Will not cause a crash when its destructor invokes (notice that we set its element pointer to nullptr, since freeing nullptr is safe), and
  2. Still lets the object be assigned a new value. This latter point is tricky, but it's important to ensure that you can still give the cleared-out object a new value at some point. This is because it is possible to obtain an rvalue reference to an object that can still be referenced later in the program.

To shed some light on (2), one interesting use case for rvalue references is the ability to explicitly move values around between objects. For example, consider this idiomatic implementation of swap:

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

This code is legal, but it's a bit unusual. In particular, it ends up making three copies - first when setting temp equal to a copy of lhs, once setting lhs to be a copy of rhs, and once setting rhs to be a copy of temp. But we don't really want to be making any copies at all here; instead, we just want to shuffle the values around. Consequently, in C++11, you'll be able to explicitly get rvalue references to objects by using the std::move function:

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

Now, no copies are made at all. We move the contents of lhs into temp, then move the contents of rhs into lhs, then moves the contents of temp into rhs. In doing so, we left both lhs and rhs in an "emptied" state temporarily before putting new values into them. It's important that when writing the code to move the contents out of an object that we leave the object in a somewhat well-formed state so that this code works correctly.

眼角的笑意。 2024-12-07 15:15:55

这不是对参考的参考。这是 C++0x 中为所谓的 右值引用引入的新语法。

It's not a reference to a reference. It's a new syntax introduced in C++0x for so-called Rvalue references.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文