本章继续跟着上章讲解C++11的新语法特性,主要包括右值引用
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
int Add(int a, int b)
{
return a + b;
}
int main()
{
//右值示例
const int&& ra = 10;
// 引用函数返回值,返回值是一个临时变量,为右值
int&& rRet = Add(10, 20);
return 0;
}
注:为了与C++98中的引用进行区分,C++11将该种方式称之为右值引用
注:左值也能放在赋值符号的右边,右值只能放在赋值符号的右边
int g_a = 10;
// 函数的返回值结果为引用
int& GetG_A()
{
return g_a;
}
int main()
{
int a = 10;
int b = a;
int* p=new int(0);
// a和b,p和*p都是左值,说明:左值既可放在=的左侧,也可放在=的右侧
const int c = 30;
//c = a;
// 特例:c虽然是左值,但是为const常量,只读不允许被修改
cout << &c << endl;
// c可以取地址,所以c严格来看也是左值
//b + 1 = 20;
// 编译失败:因为b+1的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值
GetG_A() = 100;
return 0;
}
在C++98中的普通引用与const引用在引用实体上的区别
int main()
{
// 普通类型引用只能引用左值,不能引用右值
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
//const引用既可以引用左值也可以引用右值
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
int main()
{
// 10纯右值,本来只是一个符号,没有具体的空间,
// 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量
int&& r1 = 10;
r1 = 100;
int a = 10;
int&& r2 = a; // 编译失败:右值引用不能引用左值
int&& r3=move(a);
return 0;
}
如果一个类中涉及到资源管理,用户必须显式提供拷贝构造、赋值运算符重载以及析构函数,否则编译器将会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错
class String
{
public:
String(const char* str)
{
if (nullptr == str)
return;
_str = new char[strlen(str) + 1];
strcpy(_str, str);
cout << "构造" << endl;
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
cout << "拷贝构造" << endl;
}
String & operator=(const String & s)
{
if (this != &s)
{
char* pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str;
_str = pTemp;
}
cout << "拷贝赋值" << endl;
return *this;
}
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
~String()
{
if (_str) delete[] _str;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
String s3(s1 + s2);
return 0;
}
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回
C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题
注:在C++11中如果需要实现移动语义,必须使用右值引用(上述String类增加移动构造)
String(String&& s)
: _str(s._str)
{
s._str = nullptr;
cout << "移动拷贝" << endl;
}
注:对于连续的两次移动构造,编译器会自动优化成一次,即直接省去了中间的临时变量构造
template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
// forward _Arg as movable
return ((typename remove_reference<_Ty>::type&&)_Arg);
}
int main()
{
String s1("hello world");
String s2(move(s1));
String s3(s2);
return 0;
}
注:以上代码是move函数的经典的误用,因为move将s1转化为右值后,在实现s2的拷贝时就会使用移动构造,此时s1的资源就被转移到s2中,s1就成为了无效的字符串
class Person
{
public:
Person(const char* name, const char* sex, int age)
: _name(name)
, _sex(sex)
, _age(age)
{}
Person(const Person& p)
: _name(p._name)
, _sex(p._sex)
, _age(p._age)
{}
Person(Person&& p)
: _name(move(p._name))
, _sex(move(p._sex))
, _age(p._age)
{}
private:
String _name;
String _sex;
int _age;
};
Person GetTempPerson()
{
Person p("prety", "male", 18);
return p;
}
int main()
{
Person p(GetTempPerson());
return 0;
}
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己,也就是说资源的生命周期被延长了(对象的生命周期不会改变)
除了移动构造之外,还有移动赋值
Person& operator=(Person&& p)
{
_name=move(p._name);
_sex=move(p._sex);
_age=p._age;
}
注意:
STL中的容器都是增加了移动构造和移动赋值
示例:
void push_back (value_type&& val);
int main()
{
list<String> lt;
String s1("1111");
// 这里调用的是拷贝构造
lt.push_back(s1);
// 下面调用都是移动构造
lt.push_back("2222");
lt.push_back(std::move(s1));
return 0;
}
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数
void Func(int x)
{
// ......
}
template<typename T>
void PerfectForward(T t)
{
Fun(t);
}
void Fun(int& x)
{
cout << "lvalue ref" << endl;
}
void Fun(int&& x)
{
cout << "rvalue ref" << endl;
}
void Fun(const int& x)
{
cout << "const lvalue ref" << endl;
}
void Fun(const int&& x)
{
cout << "const rvalue ref" << endl;
}
template<typename T>
void PerfectForward(T&& t)
{
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性
Fun(std::forward<T>(t));
}
int main()
{
PerfectForward(10); // rvalue ref
int a;
PerfectForward(a); // lvalue ref
PerfectForward(std::move(a)); // rvalue ref
const int b = 8;
PerfectForward(b); // const lvalue ref
PerfectForward(std::move(b)); // const rvalue ref
return 0;
}
因为引用是一个别名,需要用指针操作的地方,可以使用指针来代替,可以提高代码的可读性以及安全性
int main()
{
string s1("hello");
string s2(" world");
string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
stirng&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名
return 0;
}
注:以下代码在vs2013中不能体现,在vs2019下才能演示体现上面的特性
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
/*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:
String _name;
int _age;
};
int main()
{
Person s1{"zhangsan",18};
Person s2 = s1;
Person s3 = std::move(s1);
Person s4;
s4 = std::move(s2);
return 0;
}
注:由于可变模版参数比较抽象,使用起来需要一定的技巧,比较晦涩,现阶段呢主要掌握一些基础的可变参数模板特性
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
template <class ...Args>
void ShowList(Args... args)
{}
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " ";
ShowList(args...);//递归展开
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
template <class... Args>
void emplace_back (Args&&... args);
int main()
{
//带有拷贝构造和移动构造的String
std::list< std::pair<int, String> > mylist;
mylist.emplace_back(10, "sort");
mylist.emplace_back(make_pair(20, "sort"));
mylist.push_back(make_pair(30, "sort"));
mylist.push_back({ 40, "sort"});
return 0;
}
结论:用emplace_back更好,因为他可以不依赖参数对象是否提供移动构造