T &&(双和号)在C ++ 11中意味着什么?

我一直在研究C ++ 11的一些新特性,并且我注意到了在声明变量时的双和号,比如T&& var

一开始,这个野兽叫什么? 我希望Google允许我们搜索这样的标点符号。

这究竟意味着什么?

乍一看,它似乎是一个双引用(就像C风格的双指针T** var ),但我很难想出一个用例。


它声明了一个右值引用(标准提案doc)。

这里是对右值引用的介绍。

以下是微软标准库开发人员对rvalue引用的深入了解。 (但在阅读本文之前,请参阅此答案后的注释中的注意事项。)

C ++ 03引用(现在称为C ++ 11中的左值引用)最大的区别在于,它可以像临时值那样绑定到右值,而不必是const。 因此,这个语法现在是合法的:

T&& r = T();

右值引用主要提供以下内容:

移动语义 。 现在可以定义一个移动构造函数和移动赋值运算符,它使用右值引用而不是常用的const-lvalue引用。 移动功能就像副本一样,除非它不必保持源不变; 实际上,它通常会修改源,使其不再拥有移动的资源。 这对消除多余的副本很有用,特别是在标准库实现中。

例如,复制构造函数可能如下所示:

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

如果这个构造函数被临时传递了,那么这个副本就没有必要了,因为我们知道临时代码将被销毁; 为什么不利用临时分配的资源? 在C ++ 03中,没有办法阻止复制,因为我们无法确定我们是否通过临时。 在C ++ 11中,我们可以重载一个移动构造函数:

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

注意这里的巨大差异:移动构造函数实际上修改了它的参数。 这将有效地将临时“移动”到正在构建的对象中,从而消除不必要的副本。

移动构造函数将用于临时和非常量左值引用,它们使用std::move函数(它只是执行转换)显式转换为右值引用。 以下代码都调用f1f2的移动构造函数:

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

完美的转发 。 右值引用允许我们正确地为模板函数转发参数。 以此工厂功能为例:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

如果我们调用factory<foo>(5) ,则参数将被推断为int& ,即使foo的构造函数接受int ,它也不会绑定到文字5。 那么,我们可以使用A1 const& ,但是如果foo通过非const引用来获取构造函数参数呢? 为了建立一个真正的通用工厂函数,我们必须在A1&A1 const&上重载工厂。 如果工厂需要1个参数类型,这可能没问题,但每个附加的参数类型都会将必要的重载乘以2。这很快就不可维护。

右值引用通过允许标准库定义可正确转发左值/右值引用的std::forward函数来解决此问题。 有关std::forward如何工作的更多信息,请参阅这个出色的答案。

这使我们能够像这样定义工厂功能:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
    return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}

当传递给T的构造函数时,参数的右值/左值被保留。 这意味着如果工厂被右值调用,则T的构造函数被调用右值。 如果工厂被左值调用, T的构造函数被调用左值。 改进的工厂功能因为一个特殊规则而起作用:

当函数参数类型的形式为T&& ,其中T是模板参数,并且函数参数是类型A的左值时,类型A&被用于模板参数推导。

因此,我们可以像这样使用工厂:

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

重要的右值引用属性

  • 对于重载解析, 左值更喜欢绑定左值引用,而右值更喜欢绑定右值引用 。 因此,为什么临时工更喜欢通过复制构造函数/赋值运算符调用移动构造函数/移动赋值运算符。
  • 右值引用将隐式绑定到rvalues和隐式转换结果的临时对象 。 即float f = 0f; int&& i = f; float f = 0f; int&& i = f; 形式良好,因为float可以隐式转换为int; 该参考文献将是转换的结果。
  • 命名的右值引用是左值。 未命名的右值引用是右值。 这对了解std::move调用为什么是必需的很重要: foo&& r = foo(); foo f = std::move(r); foo&& r = foo(); foo f = std::move(r);

  • 它表示一个右值引用。 Rvalue引用只会绑定到临时对象,除非另有明确的生成。 它们用于在特定情况下使对象更加高效,并提供称为完美转发的功能,这大大简化了模板代码。

    在C ++ 03中,不能区分不可变的左值和右值的副本。

    std::string s;
    std::string another(s);           // calls std::string(const std::string&);
    std::string more(std::string(s)); // calls std::string(const std::string&);
    

    在C ++ 0x中,情况并非如此。

    std::string s;
    std::string another(s);           // calls std::string(const std::string&);
    std::string more(std::string(s)); // calls std::string(std::string&&);
    

    考虑这些构造函数背后的实现。 在第一种情况下,字符串必须执行副本以保留值语义,这涉及到新的堆分配。 但是,在第二种情况下,我们事先知道传递给我们构造函数的对象是立即破坏的,并且不必保持原样。 在这种情况下,我们可以有效地交换内部指针而不执行任何复制,这实际上更有效。 移动语义有利于任何具有昂贵或禁止复制内部引用资源的类。 考虑std::unique_ptr的情况 - 现在我们的类可以区分临时对象和非临时对象,我们可以使移动语义正常工作,以便unique_ptr不能被复制但可以移动,这意味着std::unique_ptr可以合法存储在标准容器中,排序等,而C ++ 03的std::auto_ptr不能。

    现在我们考虑右值引用的其他用法 - 完美转发。 考虑绑定对引用的引用的问题。

    std::string s;
    std::string& ref = s;
    (std::string&)& anotherref = ref; // usually expressed via template
    

    无法回想C ++ 03对此所说的内容,但在C ++ 0x中,处理右值引用时的结果类型至关重要。 对类型T的右值引用,其中T是引用类型,成为类型T的引用。

    (std::string&)&& ref // ref is std::string&
    (const std::string&)&& ref // ref is const std::string&
    (std::string&&)&& ref // ref is std::string&&
    (const std::string&&)&& ref // ref is const std::string&&
    

    考虑最简单的模板函数 - 最小值和最大值。 在C ++ 03中,你必须手动重载const和non-const的所有四种组合。 在C ++ 0x中,它只是一个重载。 结合可变模板,这可以实现完美的转发。

    template<typename A, typename B> auto min(A&& aref, B&& bref) {
        // for example, if you pass a const std::string& as first argument,
        // then A becomes const std::string& and by extension, aref becomes
        // const std::string&, completely maintaining it's type information.
        if (std::forward<A>(aref) < std::forward<B>(bref))
            return std::forward<A>(aref);
        else
            return std::forward<B>(bref);
    }
    

    因为我不记得它是如何做到的,但是min可以接受任何左值,右值,常值左值的组合。


    T&& 与类型扣除 (如用于完美转发) 一起使用的术语俗称为通用参考 。 这是由Scott Meyers在这篇文章中创造的。

    那是因为它可能是r值或l值。

    例子是:

    // template
    template<class T> foo(T&& t) { ... }
    
    // auto
    auto&& t = ...;
    
    // typedef
    typedef ... T;
    T&& t = ...;
    
    // decltype
    decltype(...)&& t = ...;
    

    请注意,标准本身没有这个概念,它只是一种讨论参考折叠规则,参考类型演绎和&&语法(单数?)组合的方法。

    更多的讨论可以在以下答案中找到:通用引用的语法

    链接地址: http://www.djcxy.com/p/6899.html

    上一篇: What does T&& (double ampersand) mean in C++11?

    下一篇: What is meant with "const" at end of function declaration?