跳到主要内容
C++11 核心特性详解:列表初始化与右值引用 | 极客日志
C++
C++11 核心特性详解:列表初始化与右值引用 深入解析 C++11 标准引入的关键特性,重点涵盖列表初始化机制与右值引用及移动语义。内容从 C++11 发展背景切入,详细对比 C++98 与 C++11 在初始化语法上的差异,阐述 std::initializer_list 的工作原理及其在容器中的应用。随后系统讲解左值与右值的本质区别,包括引用延长生命周期、参数匹配规则及引用折叠机制。文章通过模拟 string 类与 list 容器源码,演示移动构造与移动赋值如何优化深拷贝性能,并结合编译器优化策略分析传值返回与参数传递中的效率提升。最后介绍完美转发技术,帮助开发者掌握 C++11 在现代编程中的高效实践。
C++11 的发展历史
C++11 是 C++ 的第二个主要版本,也是自 C++98 以来最重要的更新。它引入了大量更改,标准化了既有实践,并改进了对 C++ 程序员可用的抽象。在它最终由 ISO 在 2011 年 8 月 12 日采纳前,人们曾使用名称'C++0x',因为它曾被期待在 2010 年之前发布。C++03 与 C++11 期间花了 8 年时间,故而是迄今为止最长的版本间隔。从那时起,C++ 有规律地每 3 年更新一次。
列表初始化
C++98 传统的{}
C++98 中一般数组和结构体可以用{}进行初始化。
struct Point { int _x; int _y; };
int main () {
int a1[] = {1 , 2 , 3 , 4 , 5 };
int a2[5 ] = {0 };
Point p1 = {1 , 2 };
return 0 ;
}
C++11 中的{}
C++11 以后想统一初始化方式,试图实现一切对象皆可用{}初始化,{}初始化也叫列表初始化。
内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化后变成直接构造。
{}初始化的过程中,可以省略掉=。
C++11 列表初始化的本意是想实现一个大一统的初始化方式,其次它在有些场景下带来很多便利,如容器 push/inset 多参数构造的对象时,{}初始化会很方便。
#include <vector>
#include <iostream>
using namespace std;
struct Point { int _x; int _y; };
class Date {
public :
Date (int year = 1 , month = , day = ) : _year(year), _month(month), _day(day) {}
( Date& d) : _year(d._year), _month(d._month), _day(d._day) {}
:
_year;
_month;
_day;
};
{
a1[] = { , , , , };
a2[ ] = { };
Point p1 = { , };
x1 = { };
Date d1 = { , , };
Date& d2 = { , , };
Date d3 = { };
Date d4 = ;
Point p2{ , };
x2{ };
Date d5{ , , };
Date& d6{ , , };
vector<Date> v;
v. (d1);
v. (Date{ , , });
v. ({ , , });
;
}
int
1
int
1
Date
const
private
int
int
int
int main ()
int
1
2
3
4
5
int
5
0
1
2
int
2
2025
2
1
const
2025
2
1
2025
2025
1
2
int
2
2025
2
1
const
2025
2
1
push_back
push_back
2025
2
1
push_back
2025
2
1
return
0
C++11 中的 std::initializer_list 上面的初始化已经很方便,但是对象容器初始化还是不太方便,比如一个 vector 对象,我想用 N 个值去构造初始化,那么我们得实现很多个构造函数才能支持。
vector<int > v1 = {1 , 2 , 3 };
vector<int > v2 = {1 , 2 , 3 , 4 , 5 };
C++11 库中提出了一个 std::initializer_list 的类,std::initializer_list il = { 10, 20, 30 };,这个类的本质是底层开一个数组,将数据拷贝过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。
容器支持一个 std::initializer_list 的构造函数,也就支持任意多个值构成的 {x1,x2,x3...} 进行初始化。STL 中的容器支持任意多个值构成的 {x1,x2,x3...} 进行初始化,就是通过 std::initializer_list 的构造函数支持的。
vector (initializer_list<value_type> il, const allocator_type& alloc = allocator_type ());
list (initializer_list<value_type> il, const allocator_type& alloc = allocator_type ());
map (initializer_list<value_type> il, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ());
template <class T >
class vector {
public :
typedef T* iterator;
vector (initializer_list<T> l) {
for (auto e : l) push_back (e);
}
private :
iterator _start = nullptr ;
iterator _finish = nullptr ;
iterator _endofstorage = nullptr ;
};
vector& operator =(initializer_list<value_type> il);
map& operator =(initializer_list<value_type> il);
#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main () {
std::initializer_list<int > mylist = {10 , 20 , 30 };
cout << sizeof (mylist) << endl;
int i = 0 ;
cout << &i << endl;
cout << mylist.begin () << endl;
cout << mylist.end () << endl;
vector<int > v1 ({1 , 2 , 3 , 4 , 5 }) ;
vector<int > v2 = {1 , 2 , 3 , 4 , 5 };
const vector<int >& v3 = {1 , 2 , 3 , 4 , 5 };
map<string, string> dict = {{"sort" , "排序" }, {"isnert" , "插入" }};
v1 = {10 , 20 , 30 , 40 , 50 };
return 0 ;
}
右值引用和移动语义 C++98 的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,C++11 之后我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
左值和右值
左值是一个表示数据的表达式(如变量名或解引用的指针),一般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时 const 修饰符后的左值,不能给他赋值,但是可以取它的地址。
右值也是一个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。
值得一提的是,左值的英文简写为 lvalue,右值的英文简写为 rvalue。传统认为它们分别是 left value、right value 的缩写。现代 C++ 中,lvalue 被解释为 locator value 的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象。而 rvalue 被解释为 read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等,也就是说左值和右值的核心区别就是能否取地址 。
#include <iostream>
using namespace std;
int main () {
int * p = new int (0 );
int b = 1 ;
const int c = b;
*p = 10 ;
string s ("11111111" ) ;
s[0 ] = 'x' ;
cout << &c << endl;
cout << (void *)&s[0 ] << endl;
int & r1 = b;
int *& r2 = p;
int & r3 = *p;
string& r4 = s;
char & r5 = s[0 ];
double x = 1.1 , y = 2.2 ;
10 ;
x + y;
fmin (x, y);
string ("11111" );
int && rr1 = 10 ;
double && rr2 = x + y;
double && rr3 = fmin (x, y);
string&& rr4 = string ("11111" );
return 0 ;
}
左值引用和右值引用
Type& r1 = x; Type&& rr1 = y; 第一个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同理,右值引用就是给右值取别名。
左值引用不能直接引用右值,但是 const 左值引用可以引用右值。
右值引用不能直接引用左值,但是右值引用可以引用 move(左值)。
move 是库里面的一个函数模板,本质内部是进行强制类型转换。
template <class _Ty> remove_reference_t <_Ty>&& move (_Ty&& _Arg) {
return static_cast <remove_reference_t <_Ty>&&>(_Arg);
}
需要注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量的属性是左值。
语法层面看,左值引用和右值引用都是取别名,不开空间。从汇编底层的角度看下面代码中 r1 和 rr1 汇编层实现,底层都是用指针实现的,没什么区别。
#include <iostream>
using namespace std;
int main () {
int * p = new int (0 );
int b = 1 ;
const int c = b;
*p = 10 ;
string s ("11111111" ) ;
s[0 ] = 'x' ;
int & r1 = b;
int *& r2 = p;
int & r3 = *p;
string& r4 = s;
char & r5 = s[0 ];
double x = 1.1 , y = 2.2 ;
10 ;
x + y;
fmin (x, y);
string ("11111" );
int && rr1 = 10 ;
double && rr2 = x + y;
double && rr3 = fmin (x, y);
string&& rr4 = string ("11111" );
const int & rx1 = 10 ;
const double & rx2 = x + y;
const double & rx3 = fmin (x, y);
const string& rx4 = string ("11111" );
int && rrx1 = move (b);
int *&& rrx2 = move (p);
int && rrx3 = move (*p);
string&& rrx4 = move (s);
cout << &r1 << endl;
cout << &rr1 << endl;
int && rrx6 = move (rr1);
return 0 ;
}
引用延长生命周期 右值引用可用于为临时对象延长生命周期,右值引用可以修改;const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。
#include <iostream>
#include <string>
using namespace std;
int main () {
string s1 = "Test " ;
string&& r2 = s1 + s1;
r2 += "yyy" ;
const string& r1 = s1 + s1;
return 0 ;
}
左值和右值的参数匹配
C++98 中,我们实现一个 const 左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。
C++11 以后,分别重载左值引用、const 左值引用、右值引用作为形参的 f 函数,那么实参是左值会匹配 f(左值引用),实参是 const 左值会匹配 f(const 左值引用),实参是右值会匹配 f(右值引用)。
右值引用变量在用于表达式时属性是左值。
#include <iostream>
using namespace std;
void f (int & x) {
cout << "左值引用重载 f(" << x << ")" << endl;
}
void f (const int & x) {
cout << "const 左值引用重载 f(" << x << ")" << endl;
}
void f (int && x) {
cout << "右值引用重载 f(" << x << ")" << endl;
}
int main () {
int i = 1 ;
const int ci = 2 ;
f (i);
f (ci);
f (3 );
int && x = 1 ;
f (x);
f (move (x));
return 0 ;
}
运行结果会显示不同重载函数的调用情况,具体取决于实参的类型。
右值引用和移动语义的使用场景
1)左值引用主要使用场景回顾 左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象。左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如 addStrings 和 generate 函数,C++98 中的解决方案只能是被迫使用输出型参数解决。那么 C++11 以后这里可以使用右值引用做返回值解决吗?显然是不可能的,因为这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用返回也无法改变对象已经析构销毁的事实。
class Solution {
public :
string addStrings (string num1, string num2) {
string str;
int end1 = num1. size () - 1 , end2 = num2. size () - 1 ;
int next = 0 ;
while (end1 >= 0 || end2 >= 0 ) {
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0 ;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0 ;
int ret = val1 + val2 + next;
next = ret / 10 ;
ret = ret % 10 ;
str += ('0' + ret);
}
if (next == 1 ) {
str += '1' ;
}
reverse (str.begin (), str.end ());
return str;
}
};
class Solution {
public :
vector<vector<int >> generate (int numRows) {
vector<vector<int >> vv (numRows);
for (int i = 0 ; i < numRows; ++i) {
vv[i].resize (i + 1 , 1 );
}
for (int i = 2 ; i < numRows; ++i) {
for (int j = 1 ; j < i; ++j) {
vv[i][j] = vv[i - 1 ][j] + vv[i - 1 ][j - 1 ];
}
}
return vv;
}
};
2)移动构造和移动赋值
移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第一个参数是该类类型的右值引用,如果还有其他参数,额外的参数必须有缺省值。
移动赋值是一个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的右值引用。
对于像 string/vector 这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类型,他的本质是要'窃取'引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,因此可以提高效率。
下面的 zsy::string 样例实现了移动构造和移动赋值,结合场景来理解。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
namespace zsy {
class string {
public :
typedef char * iterator;
typedef const char * const_iterator;
iterator begin () { return _str; }
iterator end () { return _str + _size; }
const_iterator begin () const { return _str; }
const_iterator end () const { return _str + _size; }
string (const char * str = "" ) : _size(strlen (str)), _capacity(_size) {
cout << "string(char* str)-构造" << endl;
_str = new char [_capacity + 1 ];
strcpy (_str, str);
}
void swap (string& s) {
std::swap (_str, s._str);
std::swap (_size, s._size);
std::swap (_capacity, s._capacity);
}
string (const string& s) {
cout << "string(const string& s)-拷贝构造" << endl;
reserve (s._capacity);
for (auto ch : s) push_back (ch);
}
string (string&& s) {
cout << "string(string&& s)-移动构造" << endl;
swap (s);
}
string& operator =(const string& s) {
cout << "string& operator=(const string& s)-拷贝赋值" << endl;
if (this != &s) {
_str[0 ] = '\0' ;
_size = 0 ;
reserve (s._capacity);
for (auto ch : s) push_back (ch);
}
return *this ;
}
string& operator =(string&& s) {
cout << "string& operator=(string&& s)-移动赋值" << endl;
swap (s);
return *this ;
}
~string () {
delete [] _str;
_str = nullptr ;
}
char & operator [](size_t pos) {
assert (pos < _size);
return _str[pos];
}
void reserve (size_t n) {
if (n > _capacity) {
char * tmp = new char [n + 1 ];
if (_str) strcpy (tmp, _str);
delete [] _str;
_str = tmp;
_capacity = n;
}
}
void push_back (char ch) {
if (_size >= _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2 ;
reserve (newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0' ;
}
string& operator +=(char ch) {
push_back (ch);
return *this ;
}
const char * c_str () const { return _str; }
size_t size () const { return _size; }
private :
char * _str = new char ('\0' );
size_t _size = 0 ;
size_t _capacity = 0 ;
};
}
int main () {
zsy::string s1 ("11111" ) ;
zsy::string s2 = s1;
zsy::string s3 = zsy::string ("22222" );
zsy::string s4 = move (s1);
return 0 ;
}
运行时会看到移动构造和移动赋值被调用的日志,证明资源发生了转移而非拷贝。
3)右值引用和移动语义解决传值返回问题 namespace zsy {
string addStrings (string num1, string num2) {
string str;
int end1 = num1. size () - 1 , end2 = num2. size () - 1 ;
int next = 0 ;
while (end1 >= 0 || end2 >= 0 ) {
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0 ;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0 ;
int ret = val1 + val2 + next;
next = ret / 10 ;
ret = ret % 10 ;
str += ('0' + ret);
}
if (next == 1 ) {
str += '1' ;
}
reverse (str.begin (), str.end ());
return str;
}
}
int main () {
zsy::string ret = zsy::addStrings ("11111" , "22222" );
cout << ret.c_str () << endl;
return 0 ;
}
int main () {
zsy::string ret;
ret = zsy::addStrings ("11111" , "22222" );
cout << ret.c_str () << endl;
return 0 ;
}
下图展示了 vs2019 debug 环境下编译器对拷贝的优化,左边为不优化的情况下,进行两次拷贝构造。中间为编译器的第一代优化,将连续步骤中的拷贝构造合二为一变为一次拷贝构造。
需要注意的是在 vs2019 的 release 和 vs2022 的 debug 和 release 会进行第二代优化。str 对象的构造,str 拷贝构造临时对象,临时对象拷贝构造 ret 对象,合三为一,变为直接构造。
linux 下可以将下面代码拷贝到 test.cpp 文件,编译时用 g++ test.cpp -fno-elideconstructors 的方式关闭构造优化。
我们在 vs2022 的 debug 版本下运行,会发现已经优化掉了两次拷贝构造,从而变为直接构造。
下图展示了 vs2019 debug 环境下编译器对移动的优化,左边为不优化的情况下,进行两次移动构造。中间为编译器的第一代优化,将连续步骤中移动构造合二为一变为一次移动构造。
需要注意的是在 vs2019 的 release 和 vs2022 的 debug 和 release 会进行第二代优化。str 对象的构造,str 移动构造临时对象,临时对象移动构造 ret 对象,合三为一,变为直接构造。
linux 下可以将下面代码拷贝到 test.cpp 文件,编译时用 g++ test.cpp -fno-elideconstructors 的方式关闭构造优化。
我们在 vs2022 的 debug 版本下运行,会发现已经优化掉了两次移动构造,从而变为直接构造。
右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景:
下图左边展示了 vs2019 debug 和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,一次拷贝构造,一次拷贝赋值。
需要注意的是在 vs2019 的 release 和 vs2022 的 debug 和 release,下面代码会进一步优化,优化掉拷贝构造,直接构造要返回的临时对象。str 本质是临时对象的引用,底层角度用指针实现。
我们在 vs2022 的 debug 版本下运行,可以看到优化掉了一次拷贝构造。
右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景:
下图左边展示了 vs2019 debug 和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,一次移动构造,一次移动赋值。
需要注意的是在 vs2019 的 release 和 vs2022 的 debug 和 release,下面代码会进一步优化,优化掉拷贝构造,直接构造要返回的临时对象。str 本质是临时对象的引用,底层角度用指针实现。
我们在 vs2022 的 debug 版本下运行,可以看到优化掉了一次拷贝构造。
深拷贝的自定义类型:如 vector/string/map…,实现移动构造和移动赋值有很大的价值,可以减少拷贝,提高效率。
浅拷贝的自定义类型,如 Date/pair<int,int>…,实现移动构造和移动赋值没有很大的价值,本身拷贝是很快的,对效率影响不大。
4)右值引用和移动语义在传参中的提效
查看 STL 文档我们发现 C++11 以后容器的 push 和 insert 系列的接口都增加右值引用的版本。
void push_back (const value_type& val) ;
void push_back (value_type&& val) ;
iterator insert (const_iterator position, value_type&& val) ;
iterator insert (const_iterator position, const value_type&& val) ;
当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象。
当实参是一个右值,容器内部则调用移动构造,将右值对象的资源移动到容器空间的对象上。
把我们之前模拟实现的 zsy::list 拷贝过来,支持右值引用参数版本的 push_back 和 insert。
namespace zsy {
template <class T >
struct list_node {
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node () = default ;
template <class X>
list_node (X&& data) : _data(forward<X>(data)), _next(nullptr), _prev(nullptr) { }
};
template <class T , class Ref , class Ptr >
struct list_iterator {
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;
list_iterator (Node* node) : _node(node) {}
Self& operator ++() {
_node = _node->_next;
return *this ;
}
Ref operator *() { return _node->_data; }
bool operator !=(const Self& it) { return _node != it._node; }
};
template <class T >
class list {
typedef list_node<T> Node;
public :
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
iterator begin () { return _head->_next; }
iterator end () { return _head; }
void empty_init () {
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0 ;
}
list () { empty_init (); }
list (initializer_list<T> il) {
empty_init ();
for (auto & e : il) push_back (e);
}
void push_back (const T& x) { insert (end (), x); }
template <class X>
void push_back (X&& x) { insert (end (), forward<X>(x)); }
iterator insert (iterator pos, const T& x) {
Node* newnode = new Node (x);
Node* cur = pos._node;
Node* prev = cur->_prev;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
return iterator (newnode);
}
template <class X>
iterator insert (iterator pos, X&& x) {
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node (forward<X>(x));
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
return iterator (newnode);
}
private :
Node* _head;
size_t _size;
};
}
#include "List.h"
int main () {
zsy::list<zsy::string> lt;
zsy::string s1 ("11111" ) ;
lt.push_back (s1);
cout << endl;
zsy::string s2 ("22222" ) ;
lt.push_back (move (s2));
cout << endl;
lt.push_back ("33333" );
return 0 ;
}
运行结果会显示移动构造被调用,证明右值引用参数版本生效。
类型分类
C++11 以后,进一步对类型进行了划分,右值被划分纯右值 (pure value,简称 prvalue) 和将亡值 (expiring value,简称 xvalue)。
纯右值是指那些字面值常量或者求值结果相当于字面值,又或是 一个不具名的临时对象。如:42、true、nullptr 或 str.substr(1, 2)、str1 + str2 等传值返回函数调用,或整形 a、b,a++,a+b 等。纯右值和将亡值是 C++11 才提出的,C++11 中的纯右值概念划分等价于 C++98 中的右值。
将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达,如 move(x)、static_cast<X&&>(x)。
泛左值 (generalized value,简称 glvalue),泛左值包含将亡值和左值。
引用折叠
C++ 中不能直接定义引用的引用如 int& && r = i;,这样写会直接报错,只有通过模板或 typedef 中的类型操作才可以构成引用的引用。
通过模板或 typedef 中的类型操作可以构成引用的引用时,这时 C++11 给出了一个引用折叠的规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用。
像 f2 这样的函数模板中,T&& x 参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左值时就是左值引用,传递右值时就是右值引用,这种函数模板的参数也叫做万能引用 。
Function(T&& t) 函数模板程序中,假设实参是 int 右值,模板参数 T 的推导 int,实参是 int 左值,模板参数 T 的推导 int&,再结合引用折叠规则,就实现了实参是左值,实例化出左值引用版本形参的 Function,实参是右值,实例化出右值引用版本形参的 Function。
#include <iostream>
using namespace std;
template <class T>
void f1 (T& x) {}
template <class T>
void f2 (T&& x) {}
int main () {
typedef int & lref;
typedef int && rref;
int n = 0 ;
lref& r1 = n;
lref&& r2 = n;
rref& r3 = n;
rref&& r4 = 1 ;
f1 <int >(n);
f1 <int >(0 );
f1 <int &>(n);
f1 <int &>(0 );
f1 <int &&>(n);
f1 <int &&>(0 );
f1 <const int &>(n);
f1 <const int &>(0 );
f1 <const int &&>(n);
f1 <const int &&>(0 );
f2 <int >(n);
f2 <int >(0 );
f2 <int &>(n);
f2 <int &>(0 );
f2 <int &&>(n);
f2 <int &&>(0 );
return 0 ;
}
#include <iostream>
using namespace std;
template <class T>
void Function (T&& t) {
int a = 0 ;
T x = a;
cout << &a << endl;
cout << &x << endl << endl;
}
int main () {
Function (10 );
int b = 1 ;
Function (b);
Function (move (b));
const int c = 8 ;
Function (c);
Function (move (c));
return 0 ;
}
完美转发
Function(T&&t) 函数模板程序中,传左值实例化以后是左值引用的 Function 函数,传右值实例化以后是右值引用的 Function 函数。
前面提到过,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说 Function 函数中 t 的属性是左值,那么我们把 t 传递给下一层函数 Fun,那么匹配的都是左值引用版本的 Fun 函数。这里我们想要保持 t 对象的属性,就需要使用完美转发实现。
template <class T>
T&& forward (typename remove_reference<T>::type& arg) ;
template <class T>
T&& forward (typename remove_reference<T>::type&& arg) ;
完美转发 forward 本质上是一个函数模板,他主要还是通过引用折叠的方式实现,下面示例中传递给 Function 的实参是右值,T 被推导为 int,没有折叠,forward 内部 t 被强转为右值引用返回。传递给 Function 的实参是左值,T 被推导为 int&,引用折叠为左值引用,forward 内部 t 被强转为左值引用返回。
#include <iostream>
using namespace std;
void Fun (int & x) {
cout << "左值引用" << endl;
}
void Fun (const int & x) {
cout << "const 左值引用" << endl;
}
void Fun (int && x) {
cout << "右值引用" << endl;
}
void Fun (const int && x) {
cout << "const 右值引用" << endl;
}
template <class T>
void Function (T&& t) {
Fun (forward<T>(t));
}
int main () {
Function (10 );
int a;
Function (a);
Function (move (a));
const int b = 1 ;
Function (b);
Function (move (b));
return 0 ;
}
运行结果会正确匹配对应的 Fun 重载版本,证明完美转发生效。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online