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
函数(它只是执行转换)显式转换为右值引用。 以下代码都调用f1
和f2
的移动构造函数:
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&)
重要的右值引用属性 :
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?