C++11右值引用与完美转发及可变参数模板详解
1.类型分类
• 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) • 泛左值(generalized value,简称glvalue),泛左值包含将亡值和左值,值类别-cppreference.com和Valuecategories这两个关于值类型的中⽂和英⽂的官⽅⽂档,有兴趣可以了解细节

2.引用折叠
•C++中不能直接定义引⽤的引⽤如 int& && r = i; ,这样写会直接报错,通过模板或typedef中的类型操作可以构成引⽤的引⽤。
• 通过模板或typedef中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规 则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤
• 下⾯的程序中很好的展⽰了模板和typedef时构成引⽤的引⽤时的引⽤折叠规则,⼤家需要⼀个⼀ 个仔细理解⼀下。• 像f2这样的函数模板中,T&&x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左 值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。
• Function(T&&t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模 板参数T的推导int&,再结合引⽤折叠规则,就实现了实参是左值,实例化出左值引⽤版本形参的 Function,实参是右值,实例化出右值引⽤版本形参的Function。
//引用折叠限定f1实例化后总是一个左值引用
template<class T>
void f1(T& x)
{ }
//引用折叠限定f2实例化后可以是一个左值引用,也可以是一个右值引用
template<class T>
void f2(T&& x)
{ }
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n;//r1类型int&
lref&& r2 = n;//r2类型int&
rref& r3 = n;//r3类型int&
rref&& r4 = 1;//r4类型int&&
//即只要有左值引用折叠后就是左值引用,两个都是右值引用才是右值引用
//没有折叠,实例化为void f1(int&x)
f1<int>(n);
//f1<int>(0);//err
//折叠,实例化为void f1(int&x)
f1<int&>(n);
//f1<int&>(0);//err
//折叠,实例化为void f1(int&x)
f1<int&&>(n);
//f1<int&&>(0);//err
//折叠,实例化为void f1(const int&x)
f1<const int&>(n);
f1<const int&>(0);
//折叠,实例化为void f1(const int&x)
f1<const int&&>(n);
f1<const int&&>(0);
//没有折叠,实例化为void f2(int&&x)
//f2<int>(n);//err
f2<int>(0);
//折叠,实例化为 void f2(int&x)
f2<int&>(n);
//f2<int&>(0);err
//折叠,实例化为 void f2(int&&x)
//f2<int&&>(n);err
f2<int&&>(0);
return 0;
}3.完美转发
• Function(T&&t)函数模板程序中,传左值实例化以后是左值引⽤的Function函数,传右值实例化 以后是右值引⽤的Function函数。
• 但是结合我们在5.2章节的讲解,变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定 后,右值引⽤变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传 递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性, 就需要使⽤完美转发实现。
• template T&& forward (typename remove_reference::type& arg);
•template T&& forward (typename remove_reference::type&& arg);
• 完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给 Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给 Function的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤ 返回。
没有forward即完美转发前

加了forward即完美转发后
4.可变参数模板
4.1 基本语法及原理
• C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函 数参数。
• template<class...Args> void Func(Args... args) {}
• template<class...Args> void Func(Args&... args) {}
• template<class...Args> void Func(Args&&... args) {}
• 我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则。
• 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
模板,一个函数模板可以实例化出多个不同类型参数的函数,比如template<class T>这个T可以是int类型,double类型等等
可变参数模板,一个可变参数模板实例化出多个不同参数个数的模板

4.2 包扩展
• 对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元 素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。底层 的实现细节如图1所⽰。
• C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。

//方法1:
void showlist()
{
// 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数
cout << endl;
}
template<class T,class...Args>
void showlist(T t, Args...args)
{
cout << t << " ";
// args是N个参数的参数包
// 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包
showlist(args...);
}
// 编译时递归推导解析参数
template<class...Args>
void print(Args...args)
{
showlist(args...);
}
//方法2:
template<class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template<class...Args>
void Arguments(Args...args)
{
}
template<class...Args>
void print(Args...args)
{
Arguments(GetArg(args)...);
}
//void Print(int x, string y, double z)
//{
// Arguments(GetArg(x), GetArg(y), GetArg(z));
//}4.3 emplace系列接口
• template void emplace_back (Args&&... args);
• template iterator emplace (const_iterator position, Args&&... args);
• C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上 兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container,empalce还⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
• emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列
• 第⼆个程序中我们模拟实现了list的emplace和emplace_back接⼝,这⾥把参数包不段往下传递, 最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前⾯说的empalce⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
• 传递参数包过程中,如果是 Args&&... args 的参数包,要⽤完美转发参数包,⽅式如下 std::forward(args)... ,否则编译时包扩展后右值引⽤变量表达式就变成了左值。

模拟实现List新增加emplace_back等接口
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{
template<class T>
struct list_node
{
T _data;
list_node* next;
list_node* prev;
/*list_node(const T& val = T())
:_data(val)
, next(nullptr)
, prev(nullptr)
{}*/
list_node() = default;
template <class... Args>
list_node(Args&&... args)
: next(nullptr)
, prev(nullptr)
, _data(forward<Args>(args)...)
{
}
};
template<class T,class ref,class ptr>
struct list_iterator
{
typedef struct list_iterator<T,ref,ptr> self;
typedef list_node<T> node;
node* _node;
list_iterator<T, ref, ptr>(node* nd)
:_node(nd)
{}
ref& operator*()
{
//return _node->data;
return _node->_data;
}
ptr* operator->()
{
return & _node->_data;
}
self& operator++()
{
_node = _node->next;
return *this;
}
self& operator--()
{
//return _node->prev;
_node = _node->prev;
return *this;
}
self& operator++(int)
{
_node = _node->next;
return *this;
}
self& operator--(int)
{
//return _node->prev;
_node = _node->prev;
return *this;
}
bool operator==(const self& s)const
{
return _node == s._node;
}
bool operator!=(const self& s)const
{
return _node != s._node;
}
};
template<class T>
class list
{
public:
typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T,const T&, const T*>const_iterator;
typedef list_node<T> node;
list<T>()
{
empty_init();
}
void empty_init()
{
_head = new node;
_head->next = _head;
_head->prev = _head;
}
iterator begin()
{
return iterator(_head->next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin()const
{
return const_iterator(_head->next);
}
const_iterator end()const
{
return const_iterator ( _head);
}
template <class... Args>
iterator insert(iterator pos, Args&&...args)
{
node* cur = pos._node;
node* prev = cur->prev;
node* newnode = new node(forward<Args>(args)...);
newnode->next = cur;
cur->prev = newnode;
newnode->prev = prev;
prev->next = newnode;
return cur;
++size;
}
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), forward<Args>(args)...);
}
iterator insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->prev;
node* newnode = new node(x);
newnode->next = cur;
cur->prev = newnode;
newnode->prev = prev;
prev->next = newnode;
return cur;
++size;
}
iterator insert(iterator pos, T&& x)
{
node* cur = pos._node;
node* prev = cur->prev;
node* newnode = new node(forward<T>(x));
newnode->next = cur;
cur->prev = newnode;
newnode->prev = prev;
prev->next = newnode;
return cur;
++size;
}
void pushback(const T& x)
{
insert(end(), x);
}
void push_back(T&& x)
{
insert(end(), forward<T>(x));
}
void pushfront(const T& x)
{
insert(begin(), x);
}
iterator erase(iterator pos)
{
assert(pos!=end());
node* cur = pos._node;
node* prev = cur->prev;
node* next = cur->next;
prev->next = next;
next->prev = prev;
delete cur;
return next;
--size;
}
void popback()
{
erase(--end());
}
void popfront()
{
erase(begin());
}
size_t size1()
{
return size;
}
bool empty()
{
return size == 0;
}
list<T>(list<T>&l)
{
empty_init();
for (auto& ch : l)
{
pushback(ch);
}
}
void swap(list<T>l)
{
std::swap(_head, l._head);
std::swap(size, l.size1());
}
list<T>operator=(list<T>tmp)
{
swap(tmp);
return *this;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
private:
node* _head;
size_t size;
};
template<class container>
void print( const container& con)
{
for (auto ch : con)
{
cout << ch << " ";
}
cout << endl;
}
}到此这篇关于C++11右值引用与完美转发及可变参数模板详解的文章就介绍到这了,更多相关c++11右值引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论