文章目录
- 基本概念
- 左值和左值引用
- 右值和右值引用
- 右值引用和移动语义的意义和使用场景
- 左值引用与右值引用比较
- 右值引用的特殊场景
- 左值引用的短板
- 右值引用和移动语义
- 完美转发
- 模板中的&&万能引用
- 完美转发在传参过程中保留原生类型属性
- 完美转发实际中的使用场景
基本概念
在传统的C++语法中就有引用的语法,而在C++11中新增了右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用.但是无论是左值引用还是右值引用,都是给对象取别名.
左值和左值引用
左值是一个表示数据的表达式(如变量名或者解引用的指针),我们可以获取它的地址 + 可以对它赋值.
左值既可以出现在赋值符号的左边也可以出现在赋值符号的右边,但是定义const修饰左值时,我们不能对它赋值,但是可以取它的地址.
左值引用就是给左值的引用,给左值取别名.
int main()
{
//p,b,c,*p都是左值.
int* p = new int(0);
int b = 1;
const int c = 2;
//以下几个是对上面左值的引用;
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
右值和右值引用
右值也是一个表示数据的表达式,如:字面常量(11,hello等) ,表达式的返回值,函数返回等.
右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边(右值不能被修改),右值不能取地址.
右值引用就是对右值的引用,给右值取别名.
int main()
{
//常见的右值
10;
x + y;
fmin(x + y);
//右值引用
int&& r1 = 10;
double&& r2 = x + y;
double&& r3 = fmin(x + y);
return 0;
}
右值引用和移动语义的意义和使用场景
左值引用与右值引用比较
一:左值引用总结
( 1 ) : 左值引用只能引用左值,不能引用右值.
( 2 ): 但是const左值引用既可以引用左值,也可以引用右值.
int main()
{
int a = 10;
int& ral = a; //引用左值
// int& ra2 = 10; //不能引用右值
//const修饰的左值引用既可以引用左值,也可以引用右值.
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
二: 右值引用总结
( 1 ): 右值引用只能引用右值,不能引用左值.
( 2 ): 但是右值引用可以引用move以后的左值.
int main()
{
int&& r1 = 10; //右值引用引用右值.
int a = 10;
// int&& r2 = a; //右值引用不能引用左值
int&& r3 = move(a); //右值引用可以引用move以后的左值.
return 0;
}
右值引用的特殊场景
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址.例如: 我们不能取字面量为10的地址,但是rr1引用后,不但可以对rr1取地址,还可以修改rr1, 此时rr1可以类比于左值,当然,如果不想rr1被修改,也可以const int&& rr1去引用.
int main()
{
double x = 1.1, y = 2.2;
int&& rr1 = 10; //右值引用.
const double&& rr2 = x + y;
cout << &rr1 << endl; //此时的rr1类似于左值,可以取地址,可以被修改.
rr1 = 11;
// rr2 = 3.4; //不能被修改.
}
左值引用的短板
我们先看看左值引用的作用:
那么,左值引用的盲区是什么?
例如1: 在to_string函数中,当我们使用引用返回时:
例如二: 再比如我们写得 杨辉三角 中:
针对以上情况: 在C++11以前,我们只能额外增加一个输出类型的参数,将别人需要改变的类型变量用引用接收,然后经过函数一系列的操作后返回所需要的类型,但是这样不好的缺点,就是不符合使用习惯.
所以,C++11中的右值引用一个重要功能就是要解决上面的问题.
右值引用和移动语义
为了便于以下测试展示出更好的效果,下面是我们以前模拟出来的string类,里面包含了一些最基本的成员函数,以及在拷贝构造和复制重载中新增了表示调用过该函数的提示语.并且新增了一个to_string函数.
namespace yzh
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//交换两个对象的数据
void swap(string& s)
{
//调用库里的swap
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
//拷贝构造函数(现代写法)
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
//赋值运算符重载(现代写法)
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//[]运算符重载
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//改变容量,大小不变
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strncpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//尾插字符
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_str[_size + 1] = '\0';
_size++;
}
//+=运算符重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
//返回C类型的字符串
const char* c_str()const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
//to_sting函数
yzh::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
yzh::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += (x + '0');
}
if (flag == false)
{
str += '-';
}
std::reverse(str.begin(), str.end());
return str;
}
}
以下为当我们使用to_string函数分别为编译器优化前和优化后调用拷贝次数示图:
虽然在以前时,拷贝构造(形参为const修饰) 既可以修饰左值,又可以修饰右值,但随着移动构造和移动赋值的提出,编译器会优先选择调用与函数形参最匹配的成员函数.
当实参为左值时,编译器默认调用拷贝构造.
当实参为右值时,编译器默认调用移动构造.
//移动构造
string(string&& s)
:str(nullptr)
{
cout << "string( const string&& s ) ----资源转移" << endl;
swap(s);
}
测试如下:
如果没有移动构造,即使在编译器优化后也调用了一次拷贝构造.
如果调用移动构造,那么对于编译器来说优化后就减少了一次拷贝构造.
以下为编译器优化前和编译器优化后调用移动构造图示:
完美转发
模板中的&&万能引用
在C++11中模板中的&&并不代表右值引用,而是万能引用,其节能接收左值又能接收右值.
template<class T>
void PerfectForward(T&& t)
{
//...
}
测试代码如下:
void Func(int& x)
{
cout << "左值引用" << endl;
}
void Func(const int& x)
{
cout << "const 左值引用" << endl;
}
void Func(int&& x)
{
cout << "右值引用" << endl;
}
void Func(const int&& x)
{
cout << "const 右值引用" << endl;
}
template<class T>
void PerfectForward(T&& t)
{
Func(t);
}
int main()
{
int a = 10;
PerfectForward(a); //左值
PerfectForward(move(a)); //右值
const int b = 20;
PerfectForward(b); //const 左值
PerfectForward(move(b)); //const 右值
return 0;
}
运行结果如下;
我们知道,模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,不管我们传递的实参是左值还是右值,在后续的使用中都退化成了左值.
完美转发在传参过程中保留原生类型属性
完美转发对实参会改变实参的属性,那么我们希望在传递的过程中保持实参的左值或者右值的属性,就需要我们下面学习的完美转发了.
std::forward完美转发在传参过程中保留对象原生类型属性.
为了达到该效果,我们只要在传参时调用forward模板函数就可以了.
template<class T>
void PerfectForward(T&& t)
{
Func(std::forward<T>(t));
}
代码效果如下:
我们知道,经过完美转发之后:
在传参时传入左值,编译器就会匹配到形参为左值的Fun函数.
在传参时传入右值,编译器就会匹配到形参为右值类型的Func函数.
完美转发实际中的使用场景
下面是我们曾经实现的list代码,在lsit类当中分别新增提供左值引用的和右值引用的push_back,insert函数.
namespace yzh
{
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& x = T())
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
list_node(T&& x)
:_data(std::forward<T>(x))
, _next(nullptr)
, _prev(nullptr)
{}
};
// typedef __list_iterator<T, T&, T*> iterator;
// typedef __list_iterator<T, const T&, const T*> const_iterator;
// 像指针一样的对象
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> iterator;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef ptrdiff_t difference_type;
Node* _node;
// 休息到17:02继续
__list_iterator(Node* node)
:_node(node)
{}
bool operator!=(const iterator& it) const
{
return _node != it._node;
}
bool operator==(const iterator& it) const
{
return _node == it._node;
}
// *it it.operator*()
// const T& operator*()
// T& operator*()
Ref operator*()
{
return _node->_data;
}
//T* operator->()
Ptr operator->()
{
return &(operator*());
}
// ++it
iterator& operator++()
{
_node = _node->_next;
return *this;
}
// it++
iterator operator++(int)
{
iterator tmp(*this);
_node = _node->_next;
return tmp;
}
// --it
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
// it--
iterator operator--(int)
{
iterator tmp(*this);
_node = _node->_prev;
return tmp;
}
};
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;
//typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
//typedef __reverse_iterator<const_iterator, const T&, const T*> //const_reverse_iterator;
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
/*reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}*/
void empty_init()
{
// 创建并初始化哨兵位头结点
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
list()
{
empty_init();
}
void swap(list<T>& x)
//void swap(list& x)
{
std::swap(_head, x._head);
}
// lt2(lt1)
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void push_back(const T& x)
{
//Node* tail = _head->_prev;
//Node* newnode = new Node(x);
_head tail newnode
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
void push_back(T&& x)
{
insert(end(), std::forward<T>(x));
}
void push_front(const T& x)
{
insert(begin(), x);
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
// prev newnode cur prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(std::forward<T>(x));
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
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 iterator(next);
}
private:
Node* _head;
};
}
在list类型存储的数据类型就为我们模拟实现的string类,此时,我们分别调用list中的push_back.
int main()
{
yzh::list<yzh::string> lt;
yzh::string s("1111");
lt.push_back(s); //调用左值引用版本的push_back
lt.push_back("2222"); //调用右值引用版本的push_back
return 0;
}
图示相关解释如下: