文章目录
十九、C++11
6. 右值引用和移动语义
万能引用和完美转发
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
#include <iostream>
using namespace std;
template<typename T>
void PerfectForward(T&& t)
{
cout << "PerfectForward(T&& t)" << endl;
}
int main()
{
// 右值
PerfectForward(10);
int a;
// 左值
PerfectForward(a);
// 右值
PerfectForward(move(a));
const int b = 8;
// const 左值
PerfectForward(b);
// const 右值
PerfectForward(move(b));
return 0;
}
无论是左值还是右值,都成功调用了函数模板。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
#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<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
// 右值
PerfectForward(10);
int a;
// 左值
PerfectForward(a);
// 右值
PerfectForward(move(a));
const int b = 8;
// const 左值
PerfectForward(b);
// const 右值
PerfectForward(move(b));
return 0;
}
这种情况该怎么办呢?我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用 完美转发 。forward 。
#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;
}
// forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
Fun(forward<T>(t));
}
int main()
{
// 右值
PerfectForward(10);
int a;
// 左值
PerfectForward(a);
// 右值
PerfectForward(move(a));
const int b = 8;
// const 左值
PerfectForward(b);
// const 右值
PerfectForward(move(b));
return 0;
}
7. 新的类功能
新的默认成员函数
原来C++类中,有6个默认成员函数:①构造函数②析构函数③拷贝构造函数④复制重载函数⑤取地址重载函数⑥const取地址重载函数。最重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个默认成员函数:移动构造函数 和 移动赋值运算符重载。
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝(浅拷贝),自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝(浅拷贝),自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
类成员变量初始化
现在类的成员变量可以在声明处给缺省值了。成员变量没初始化就会使用缺省值。
default
default 关键字可以 强制生成指定默认成员函数。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
#include <iostream>
#include <string>
using namespace std;
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(Person&& p) = default;
private:
string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = move(s1);
return 0;
}
delete
delete 关键字可以 禁止生成指定默认成员函数。如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
// 禁止生成默认的拷贝构造函数
Person(const Person& p) = delete;
private:
string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = move(s1);
return 0;
}
继承和多态中的final与override关键字
final可以修饰一个类,这个类会变成最终类,不能再被继承。
override可以修饰一个虚函数,强制虚函数进行重写,如果没有对这个虚函数进行重现,将会报错。
8. 可变参数模板
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板。
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{
}
可变参数模板的原理是怎样的呢?可变参数模板是在 编译时递归处理参数包 的。
#include <iostream>
#include <string>
using namespace std;
// 递归终止函数
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', string("sort"));
return 0;
}
STL容器中的empalce相关接口函数
STL中empalce相关结构就使用了可变参数模板。
等等等。
确实这个函数跟普通的push系列函数功能都是一样的都是插入数据。
#include <iostream>
#include <string>
#include <list>
using namespace std;
int main()
{
list<pair<int, char>> list;
// emplace系列是直接把参数当列表形式传入
list.emplace_back(10, 'a');
list.emplace_back(20, 'b');
list.emplace_back(make_pair(30, 'c'));
list.push_back(make_pair(40, 'd'));
list.push_back({ 50, 'e' });
for (auto e : list)
cout << e.first << ":" << e.second << endl;
return 0;
}
有区别的是:
push系列函数在对于深拷贝的对象进行插入时,会调用一次构造+一次移动构造,但是emplace系列仅仅只会调用一个构造函数,它是将参数一个一个传入对象的,省去了移动构造的消耗。
push系列函数对于浅拷贝的对象进行插入时,会调用一个构造+一次拷贝构造,但是emplace系列同理,将参数一个一个传入对象,也省去了拷贝构造的消耗,只会调用一次构造函数。
综上所述,对于深拷贝的对象,由于移动构造本身消耗就小,所以使用push系列或者emplace系列都可以;对于浅拷贝的对象,emplace则能够省去一个拷贝构造的消耗,优势还是蛮大的。所以建议使用emplace系列插入数据。