0. 泛型编程
-
c++编程范式之一
-
强类型程序设计
- 参与运算的所有对象的类型在编译时就已确定,并且编译程序会进行严格地类型检查
- 解决强类型程序设计的严格性和灵活性的冲突
宏函数、重载函数、模板
-
模板
实现代码复用,又称为代码生成器
-
实现原理:
- 将类型参数化
- 模板参数推导
-
编译器对于模板的推导,需要进行 显示实例化 或者 隐式实例化 才能正常进行
-
函数模板
-
若已有实例化的模板函数,则编译器不会进行推导
-
函数模板之间可以进行重载
-
函数模板参数列表 template<typename T, …>
-
实例化
- 隐式实例化
- 显式实例化
需在"<>"中指定,不能为空
-
-
-
模板参数类型
-
类型参数
关键字
class
/typename
进行修饰 -
非类型参数/常量表达式
与宏常量、const常量的区别:
- 宏常量、const常量在定义之后就已经确定了
- 而非类型参数可以在实例化时进行显式指定
-
模板的实例化
-
完全实例化
只是对于 通用模板实例化 的一种 特定类型 的实例化,仍然是一个模板
- 完全实例化之前必须要有通用模板实例化
-
部分实例化/偏实例化
-
-
类模板成员函数的实例化
-
默认情况下,一个类模板的成员函数只有当程序中使用其时才进行实例化
-
对于类模板中的类型成员以及普通成员的区分
- 通过作用域限定符::访问到的成员是普通成员
- 若要访问类型成员,需要关键字
typename
声明
-
对于类模板的静态成员对象,除了所必要的初始化以外,还要注意只有在使用了该对象成员后,其初始化才会进行
-
-
可变模板参数
template<class Type> void printAll(Type t) { cout << t << endl; } template<class Type, class...Args> void printAll(Type t, Args... args) { #if 1 cout << t << endl; printAll(args...); #else int arr[] = { (printAll(args),0)... }; #endif }
-
模板参数包
template<class...Args>
-
函数参数包
ReturnType FuncName(Args... args)
-
打包
… 位于参数包之前
-
解包
… 位于参数包之后
-
求取可变参数的个数
sizeof...
-
-
1. 移动语义
提出的目的:
- 为了解决临时对象不必要的资源浪费
- 为了充分利用临时对象申请的资源而进行的资源转移
-
右值引用
当 const引用 作为函数形参时,无法识别出绑定的是左值还是右值,因此c++11引入 右值引用进行转移操作
- 转移操作的执行时机: 当传递的是一个右值时
- 右值引用只能绑定到右值,不能绑定到左值
- 为了使得左值也可以进行绑定,需要进行
std::move()
函数 进行类型转换
-
在移动操作之后,移动源对象必须保持有效、可析构的状态,但对其值不能做任何假设
-
具有移动语义的函数要优于 具有复制控制语义的函数
-
编译器不会默认合成具有移动语义的函数(移动构造函数、移动赋值运算符函数)
-
定义了具有移动语义函数的类,如果不定义复制控制函数,则默认是删除的
-
只有当一个类没有定义自己的拷贝构造函数时,如果类的所有非静态数据成员都可移动时,编译器才会为其合成一个移动构造函数
- 编译器可以移动 内置类型
- 对于一个成员对象所对应的类定义了移动构造函数,则该数据成员也可移动
-
对于移动操作,如果不抛出异常,则将应其声明为
noexcept
;否则,编译器会认为其有可能抛出异常,从而执行一些额外的操作- 对于
vector
的自动扩容而言,当执行push_back
操作时,会申请一个新的堆空间,并将原堆空间中的内容拷贝至新的堆空间 - 若定义了一个不抛出异常的移动操作,为了使得
vector
自动扩容时使用该移动操作,则需将该移动操作声明为noexcept
的,否则,编译器会认为其有可能抛出异常,因此会调用复制控制函数
- 对于
-
当类中定义了移动构造函数以及拷贝构造函数之后,拷贝构造函数发生的时机发生了变化:
- 在函数调用返回一个类对象时
- 若返回的是一个 即将被销毁的对象,则调用 移动构造函数
- 若返回的是一个 持久化的对象,则调用 拷贝构造函数
- 在函数调用返回一个类对象时
2. 资源管理
在c语言中,资源回收是靠程序员来确保的,并不能保证资源100%被回收
2.1 RAII技术
Resource Acquisition Is Initialization
-
本质特征
利用栈对象的生命周期来管理资源
-
特性
- 栈对象创建时分配资源
- 栈对象销毁时回收资源
- 还定义了一些 用于访问资源的方法
- 一般情况下,不允许复制
2.2 智能指针
-
auto_ptr
-
c++0x版本,c++17中弃用
auto_ptr(auto_ptr&);
该函数底层实现的是移动语义 -
release
-
reset
-
get
-
-
unique_ptr
-
表达对象语义,即不能复制、赋值
表达对象语义的方法:
- 复制控制函数 私有化
- 复制控制函数 删除
- 继承一个对象语义的类
unique_ptr底层实现采用该方法
-
提供了移动构造函数
-
独享资源的所有权
-
-
shared_ptr
-
表达值语义
-
共享资源所有权
实现:引用计数
-
若一个堆类对象被托管给
shared_ptr
智能指针,那么在该类中为了使用其对应的shared_ptr
,需要继承enable_shared_from_this<>
模板类,使用其共有方法shared_from_this()
来获取对应的shared_ptr
-
-
weak_ptr
-
弱引用智能指针
-
引入目的
-
解决 shared_ptr 的循环引用问题
class Parent; class Child { public: Child() : _pParent(nullptr) {} std::shared_ptr<Parent> _pParent; }; class Parent { public: Parent() : _pChild(nullptr) {} std::shared_ptr<Child> _pChild; }; shared_ptr<Child> pChild(new Child()); shared_ptr<Parent> pParent(new Parent()); pChild->_pParent = pParent; pParent->_pChild = pChild;
当栈对象pChild
,pParent
销毁之后,
-
-
当进行复制时,引用计数不会自增
-
不能直接访问托管的资源,需要通过
lock()
方法提升为shared_ptr
- 若提升成功
lock
函数返回对应的shared_ptr
,失败返回nullptr
- 若提升成功
-
可以检查托管资源是否有效
expried()
-
2.3 删除器
-
默认情况下,智能指针只能回收 指向堆空间的指针,因为在资源回收时使用
delete
- 对于指向堆空间的指针,使用
delete
或delete[]
- 对于文件指针,使用
fclose()
- 对于指向堆空间的指针,使用
-
通过使用删除器,可以指定资源回收的方式
//默认删除器模板,使用delete进行回收资源 template<class Type> struct DefaultDelete { void operator()(Type * pType) { if(pType) { delete pType; pType = nullptr; } } }; //默认删除器模板,使用delete[]进行回收资源 template<class Type> struct DefaultDeleteArray { void operator()(Type * pType) { if(pType) { delete[] pType; pType = nullptr; } } }; //完全特列化,针对FILE类型,使用fclose进行回收资源 template<> struct DefaultDelete<FILE> { void operator()(FILE * pType) { if(pType) { fclose(pType); pType = nullptr; } } }; //RAII的模板实现,表达对象语义 template<class Type, class Deleter = DefaultDelete<Type>> class RAII { public: RAII(Type * pType) : _pType(pType) { } RAII(const RAII &) = delete; RAII & operator=(const RAII &) = delete; RAII(RAII && rhs) { if(this != rhs) { _pType = release(); _deleter = rhs._deleter; } } ~RAII() { _deleter(_pType); } Type * get() const { return _pType; } void reset(Type * pType) { if(_pType != pType) { _deleter(_pType); _pType = pType; } } Type * operator->() { return _pType; } Type& operator*() { return *_pType; } private: Type * release() { Type * pTmp = _pType; _pType = nullptr; return pTmp; } private: Type * _pType; Deleter _deleter; };