本节书摘来自异步社区出版社《Visual C++ 2012 开发权威指南》一书中的第2章,第2.2节,作者: 尹成 , 朱景尧 , 孙明龙 , 胡耀文,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.2 Visual C++2012的语言新特性(2)
Rvalue引用:N1610"Rvalues类对象的初始化的澄清"是早期尝试启用无rvalue引用move 语意。
这些新规则还没有完全实现VC11开发者预览中。
Rvalue引用v3.0添加自动生成的构造函数和移动赋值运算符在一定条件下的新规则。这不会进行中VC11,还将继续遵循的永远不会自动生成move构造函数/移动页本页的行为。
移动语义
Rvalue引用支持移动语义的实现,可以显著提高应用程序的性能。移动语义使能够调用资源编写代码(如动态分配的内存)来自对象到另一个。移动语义工作,因为它可以使资源从一个程序不能在其他位置引用的临时对象。
若要实现移动语义,通常会提供一个移动构造函数和(可选)为移动赋值运算符(operator=)的类。数据源自动是rvalues构造的副本并分配操作利用移动语义。不同于默认复制构造函数,编译器不提供默认构造函数移动。
还可以重载普通函数和运算符利用移动语义。Visual C++2012介绍移动语义到标准模板库(STL)。例如,string类实现执行移动语义的操作。考虑连接几个字符串并将结果输出到的示例:
// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = string("h") +"e" +"ll" +"o";
cout << s << endl;
}```
之前,每个调用operator+分配并返回新临时string对象(rvalue)。operator+不能追加一个字符串。因为它不知道源字符串是否是lvalue或rvalues。如果源字符串都是lvalue,它们在程序中可能在其他位置引用并不能修改。使用rvalue引用,operator+可以修改rvalues,此过程不能在其他位置引用。因此,operator+现在可追加一个字符串到另一个。这样可以显著减少string类必须执行动态内存分配数。有关string类的更多信息,请参见basic_string的类。
移动语义还有助于,当编译器无法使用返回值优化(RVO)期间或命名返回值优化(NRVO)。在这些情况下,如果类型定义为移动语义,编译器将调用构造函数。有关命名的更多信息返回值优化,请参见Named Return Value Optimization in Visual C++2005。
更好地了解移动语义,请考虑插入元素的示例。vector对象。如果vector对象的容量会溢出,vector对象必须重新分配其元素的内存然后复制每个元素向另一个内存位置腾出空间。该插入的元素。在插入操作复制一个组件时,它将创建一个新元素,调用复制构造函数将前面的元素数据复制到新元素,然后销毁上一个元素。移动语义可以将对象直接,而不必执行开销很大的内存分配和复制操作。要在vector示例中的移动语义,可以编写移动数据移动构造函数对象到另一个。
有关移动语义的引入的更多信息。在Visual C++2012的STL中,请参见标准C++库参考。
完全转发
完全转发减少对重载函数的需要,并有助于避免转发问题。转发问题,可能会在泛型函数中采用引用编写,其参数和它通过(或向前)这些参数传递给另一个函数。例如,如果泛型函数采用类型const T&的参数,然后调用函数不能修改该参数的值。如果泛型函数采用类型T&的参数,则函数不能调用使用rvalue(如临时对象或整数值)。通常,解决此问题,必须提供采用T& 和const T& 其参数中的泛型函数的重载版本。因此,重载函数成指数增加带参数的数目。Rvalue引用编写接受任意参数前向到另一个功能的版本,就象另一个函数直接调用。
考虑声明四类型、W、X、Y和Z的示例。每个类型的构造函数采用const的不同组合,而非const lvalue引用作为其参数。
struct W
{
W(int&, int&) {}
};
struct X
{
X(const int&, int&) {}
};
struct Y
{
Y(int&, const int&) {}
};
struct Z
{
Z(const int&, const int&) {}
};`
假定需要编写生成对象的泛型函数。下面的示例演示一种此函数的编写:
template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
return new T(a1, a2);
}```
下面的示例显示活动调用factory功能:
int a = 4, b = 5;
W* pw = factory(a, b);`
但是,下面的示例不包含有效调用factory功能,因为factory作为左值可以使用引用rvalues,可修改的作为其参数,但是,它调用:
Z* pz = factory<Z>(2, 2);
通常,解决此问题,必须创建factory函数的重载版本A& 和const A& 参数的每种组合。如下面的示例所示,Rvalue引用编写factory功能的版本,例如:
template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}```
在向factory的参数函数,此示例使用rvalue引用。std:: 向前 功能的用途是向前工厂函数的参数传递给模板类的构造函数。
下面的示例演示使用修改后的factory函数创建W、X、Y和Z类的实例的main功能。修订了factory功能向前其参数(lvalue或rvalues)为相应的类构造函数。
int main()
{
int a = 4, b = 5;
W* pw = factory(a, b);
X* px = factory(2, b);
Y* py = factory(a, 2);
Z* pz = factory(2, 2);
delete pw;
delete px;
delete py;
delete pz;
}`
Rvalue其他属性引用
可以重载函数采用了lvalue引用,并且rvalue引用。
通过重载函数采用const lvalue引用或rvalue引用,可以区分不能更改的对象中编写代码(lvalue)和可修改的临时值(rvalues)之间。可以传递给采用rvalue引用的功能的对象,除非该对象标记为const。下面的示例演示函数f,重载带有lvalue引用,并且有rvalue引用。main函数调用与有lvalue和rvalue的函数f。
// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// 使用内存资源的类
class MemoryBlock
{
// 在此处为该类添加资源
};
void f(const MemoryBlock&)
{
cout << "In f(const MemoryBlock&).This version cannot modify the parameter." << endl;
}
void f(MemoryBlock&&)/
{
cout << "In f(MemoryBlock&&).This version can modify the parameter." << endl;
}
int main()
{
MemoryBlock block;
f(block);
f(MemoryBlock());
}```
该示例产生下面的输出:
![image](https://yqfile.alicdn.com/7dd83c3ff7e409e1b5a9af522c8d2d82f2b2c0a2.png)
在此示例中,第一次调用f通过局部变量(左值)作为其参数。第二次调用f通过临时对象作为其参数。由于临时对象在程序中不能在其他位置引用,调用将为接受rvalue引用,可以*修改对象f的重载版本。在左值和未命名的rvalue引用用作rvalue,编译器将一个名为rvalue引用。
在函数采用rvalue引用作为参数的写权限,该参数视为该函数体中的一个lvalue。因为命名对象可以由程序的几部分引用编译器将一个名为rvalue引用作为左值;允许程序的多个部件对该对象修改或移除资源是危险的。例如,因此,如果程序的多个部件尝试调用同一对象资源,因此,只有第一部分成功调用该资源。
下面的示例演示函数g,重载带有lvalue引用,并且有rvalue引用。函数f采用rvalue引用作为其参数(一个名为rvalue引用)并返回rvalue引用(未命名的rvalue引用)。在对g的调用从f,重载决策选择采用lvalue引用g的版本,因为f体将其参数用作左值。在对g的调用从main,重载决策选择采用rvalue引用g的版本,因为f返回rvalue引用。
// named-reference.cpp
// Compile with: /EHsc
include
using namespace std;
//使用内存资源的类.
class MemoryBlock
{
//在此处为该类添加资源.
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
}
MemoryBlock&& f(MemoryBlock&& block)
{
g(block);
return block;
}
int main()
{
g(f(MemoryBlock()));
}`
该示例产生下面的输出:
在此示例中,main函数传递rvalue到f。f体将其命名参数用作左值。从f的调用。g将参数传递给左值引用(g的第一个重载版本)。
可以将左值定义为rvalue引用。
STL std:: 移动功能可以转换为rvalue的对象对该对象的引用。或者,如下面的示例所示,可以使用static_cast关键字左值转换为rvalue引用,例如:
// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
//使用内存资源的类
class MemoryBlock
{
//在此处为该类添加资源.
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
}
int main()
{
MemoryBlock block;
g(block);
g(static_cast<MemoryBlock&&>(block));
}```
该示例产生下面的输出:
![image](https://yqfile.alicdn.com/ba513c17d25b4eb0fb6e480861450fdce524eb27.png)
函数模板推导出自己的模板参数类型然后使用引用折叠的规则。
类的方法常见的做法是传递函数模板(或向前)的参数到另一个函数。有必要知道的模板类型推导如何为采用rvalue引用的函数模板。
如果函数参数是rvalue,编译器推导参数是rvalue引用。例如,如果通过rvalue引用类型X对象为接受类型T&& 作为参数的模板函数,模板参数推导T是X。因此,参数具有类型X&&。如果函数参数是一个lvalue或const lvalue,编译器推断其类型是lvalue引用或const lvalue引用该类型。
下面的示例声明一个结构模板随后专用它的各种引用类型。print_type_and_value函数采用rvalue引用作为其参数前向到S::print方法的适当的专用版本。main功能演示各种调用S::print方法。
// template-type-deduction.cpp
// Compile with: /EHsc
include
include
using namespace std;
template struct S;
// 下面分别制定S为
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), const rvalue reference (const T&&).
// 每个结构提供输出函数来输出每个结构的参数和类型.
template struct S {
static void print(T& t)
{
cout << "print: " << t << endl;
}
};
template struct S {
static void print(const T& t)
{
cout << "print: " << t << endl;
}
};
template struct S {
static void print(T&& t)
{
cout << "print: " << t << endl;
}
};
template struct S {
static void print(const T&& t)
{
cout << "print: " << t << endl;
}
};
template void print_type_and_value(T&& t)
{
S::print(std::forward(t));
}
// 此函数返回字符串 "fourth".
const string fourth() { return string("fourth"); }
int main()
{
string s1("first");
print_type_and_value(s1);
const string s2("second");
print_type_and_value(s2);
// 下面的调用可以解析为:print_type_and_value(string&& t)
print_type_and_value(string("third"));
//下面的调用可以解析为:print_type_and_value(const string&& t)
print_type_and_value(fourth());
}`
该示例产生下面的输出:
若要解决每次调用print_type_and_value函数,编译器先执行模板参数推导。会进行相应参数类型转换时,用模板参数的编译器将引用折叠的规则。例如,通过局部变量s1到print_type_and_value函数导致编译器生成下列函数签名:
print_type_and_value<string&>(string& && t)
编译器使用引用折叠的规则使该签名减少到以下操作:
print_type_and_value<string&>(string& t)
print_type_and_value功能的此版本向前然后它对S::print方法的正确的专用版本的参数。
下表总结了模板参数推导类型的引用折叠的规则:
模板参数推导是实现完全转发的元素。