传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋 值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。右值引用就是对右值的引用,给右值取别名。
左值有何右值最关键的区别就是是否能对他取地址。
int main()
{
// 左值引用只能引用左值,不能引用右值。
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;
const int& ra4 = a;
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int b = 10;
//int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(b);
return 0;
}
const左值引用既可引用左值,也可引用右值。
右值不能引用左值,但可以引用move以后的左值。
左值引用在函数传参时减少了拷贝,但在接收函数返回值时却并没有发挥作用。
比如下面这个情况:
template
T func2(const T& x)
{
T y = x;
// ...
return y;
}
如果T类型是一个自定义类型,需要深拷贝,y内部的资源并没有被完全利用起来。
在没有右值引用时采用输出型参数解决问题,但比较别扭。
右值引用的价值一:为了解决引用返回局部变量。解决问题的方式不在于函数本身,而在于返回类型的拷贝构造。
首先引入我们自己写的szg::string:
namespace szg
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
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);
}
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
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];
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)
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0; // 不包含最后做标识的\0
};
我们在string中写入左值拷贝构造:
// 移动构造
string(string&& s)
{
cout << "string(const string& s) -- 移动拷贝" << endl;
swap(s);
}
左值拷贝构造: string(const string& s) 是模仿(深拷贝)
右值拷贝构造(移动构造) string(string&& s) 是掠夺(资源转移)
右值不能取地址,但是右值引用对右值取别名,右值被储存到特定位置,且可以取到该位置地址,右值引用后属性变左值。
int main()
{
//szg::string ret = szg::to_string(-1234);
szg::string copy;
copy = szg::string();
return 0;
}
在string中写入:
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string s) -- 移动赋值" << endl;
swap(s);
return *this;
}
插入一些右值数据也可以减少拷贝。
int main()
{
list lt;
szg::string s1("111111");
lt.push_back(s1);//左值,深拷贝
lt.push_back(szg::string("222222"));//右值,移动拷贝
lt.push_back("333333");//右值,移动拷贝
return 0;
}
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。
注意:万能引用仅仅对模板函数有效。
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
void PerfectForward(T&& t)
{
// t可能是左值,可能是右值
Fun(t);
// 完美转发,保持他属性
//Fun(std::forward(t));
//t++;
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
也就是说,虽然看上去只写了一个模板函数,但在传递不同类型的值时,模板实例化为相应的函数,并不只是接收右值,可以说是把泛型做到极致了。
因为有了万能引用:
template
void PerfectForward(T&& t)如果没有万能引用就要写四个模板函数为:
template
void PerfectForward(T& t)template
void PerfectForward(T&& t)template
void PerfectForward(const T& t)template
void PerfectForward(const T&& t)
但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
如果上述的程序没有使用完美转发,在二次使用时退化为左值。
如果不做处理或者使用move处理,传递的实参属性全为左值或者全为右值:
那么如果我们想要保持属性,让我们传进去的时候是什么属性,调用的时候就使用什么属性,那就要使用完美转发。
默认成员函数
原来C++类中,有6个默认成员函数:
1. 构造函数
2. 析构函数
3. 拷贝构造函数
4. 拷贝赋值重载
5. 取地址重载
6. const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。 C++11 新增了两个:移动构造函数和移动赋值运算符重载。
默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
default: C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原 因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以 使用default关键字显示指定移动构造生成。
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
/*Person(Person&& p)
:_name(std::forward(p._name))
, _age(p._age)
{}*/
// 强制生成
Person(Person&& p) = default;
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
Person& operator=(const Person& p)
{
if(this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}
~Person()
{}
private:
szg::string _name;
int _age;
};