1、静态多态和动态多态
静态多态:函数重载,模板。编译期间完成。
动态多态:虚函数。运行期间实现。
2、模板的实现和优缺点
函数模板的代码并不能直接编译成二进制代码,而是要实例出一个模板实例。写了模板的实现(定义)后,编译器在遇到需要实例的情况时才会进行实例。
优点:
增加了代码的重用性和可扩展性。减少开发时间。
缺点:
1.编译器不支持对模板的分离式编译,即所有基于模板的算法的定义实现都要放在头文件中,否则编译时可能不能生成模板的实例,使得在链接时报错。(也就是说,模板的定义都要放在.h文件中,不能声明放.h定义放.cpp)
2.可读性不好,调试困难。
3、构造、析构函数和抛出异常
能在析构函数中抛出异常吗?
语法上可以,但是实际中不能抛出异常。
1. 析构函数中抛出异常会打断他做的事情,可能无法释放申请的内存。
2. 如果无法避免析构函数抛出异常,那么应该让析构函数捕获任何异常,然后选择消化异常或者终止程序。
3. 析构函数抛出异常,然后进行栈展开寻找异常处理,栈展开过程中会自动清空函数的局部变量,调用函数中对象的析构函数,如果这时候又抛出一个异常,因为C++只能执行一个异常,程序会自动调用std::terminate(),终止程序。
能在构造函数中抛出异常吗?
构造函数可以抛出异常,但是要避免内存泄露。
1. 构造函数中抛出异常,该对象就没有被完全构造,就不会调用析构函数。那么已经new的对象就不会被释放,可能造成内存泄漏。
2. 解决的方法:一个是使用智能指针;二是捕获所有的异常,然后把申请的资源释放,再重新抛出异常。
4、构造、析构函数和虚函数
语法可以,但是实际中不能这么做。
不能在构造函数中调用虚函数?
在构建派生类对象时,基类的构造函数会先被调用,如果基类的构造函数中调用了虚函数,这时候调用的虚函数是基类版本的虚函数,而不是预想中的派生类的虚函数。因为基类的构造函数先调用,此时派生类的成员对象还未初始化,所以调用派生类的虚函数是不安全的。而且虚函数表指针是在构造函数中初始化的,不初始化调用不了正确的虚函数。
不能在析构函数中调用虚函数?
同理,派生类会先析构,再调用父类的析构函数。如果此时父类的析构函数调用了虚函数,那么只能调用父类版本的虚函数。
5、STL里的内存池实现
内存池概念
如果每次申请内存都向系统申请,因为操作系统分配内存很费时的,那么频繁的申请空间就会很耗时。内存池就是先向系统申请一大块内存,然后用户需要的时候再划分一部分给用户,这样效率高。
两级配置器
对于分配128byte的空间,使用第一级配置器以malloc,free实现;
分配小于128byte空间,则使用第二级配置器,用内存池管理。每次配置一大块内存,维护对应的16个自由链表(free-list)。自由链表分别管理大小为8、16、24、...、128的数据块,每次对于申请的大小在自由链表中找出一个满足的块。
指针数组+自由链表
内存池实现:
指针数组中第一个指针指向8byte块的自由链表,第二个指向16byte块自由链表,以此类推。
要分配一个块,那么先找出第一个满足要求的8的倍数的块,然后在指针数组中找到对应的位置,如果还有空闲块,就分配链表头的块出去,更新链表头的块;如果不足,则重新申请(默认20个)空闲块,然后分配。回收时同理,把回收的块放到链表头。
union obj{
union obj *next;//当使用next时、Obj所代表的的就是指向下一个节点的指针
char client[1]; //当使用data时、Obj本身就是实际的块空间
}
参考资料:
C++模板的定义是否只能放在头文件中?
Effective C++读书笔记(8): 析构函数不能抛出异常
SGI STL中内存池的实现