Effective C++ 16-20 Note
条款16:成对使用new和delete时要采用相同形式
如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中没有使用new,一定不要在相应的delete表达式中使用[]
单一对象的内存布局与数组的内存布局不一样,数组需要额外的空间记录数组大小,可以把两者的内存情况理解为下图:
如果对一个单一对象使用delete[],编译器会按照删除数组的形式寻找对应的n,读取到本不应该存在的n。然后错误的多次析构。
而对数组对象使用delete,会漏掉很多对象没有删除。
条款17:以独立语句将newed对象置入只能指针
考虑如下语句:
而这三件事的顺序是不一定的,可能会出现如下顺序:
如果在调用priority时发生了异常,就会因为new出来的对象没能delete掉而导致内存泄漏。
所以我们需要将new和智能指针的构造绑定在一起,形成一个独立语句,在priority被调用之前执行:
条款18:让接口容易被正确使用,不易被误用
“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容
尽量让你的types的行为与内置types一致,并且与内置类型兼容。
“阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
建立新类型:
这个函数可能会因为不同国家表示时间的方式不同而被误用,如果我们创建month、day、year三个新类型,并且将这三个类型作为构造函数的实参,就不会引起误用:
**束缚对象值:**可以在新类型里面给出该类型对象的取值范围,具体做法时在类内编写静态成员函数,返回合理的对象值
限制类型上的操作:
**消除客户的资源管理责任:**任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事。
例如函数返回一个new指针(server start),要求用户在使用完后记得手动delete掉(server close)。用户有时候可能会忘记close,从而导致内存泄漏。而将其改为智能指针,就可以避免此问题。
tr1::shared_ptr支持定制型删除器,这可以防范DLL问题,可被用来自动接触互斥锁等等
条款19:设计class 犹如设计type
在设计一个class的时候,问自己下面这些问题
1. 新type对象应该如何被创建和销毁?
这会影响到构造函数和析构函数以及内存分配函数和释放函数的设计
2. 对象的初始化和对象的赋值该有什么样的差别?
这会影响到构造函数和赋值操作符的行为。最重要的是不要混淆了“初始化”和“赋值”
3. 新type对象如果被pass by value,意味着什么
copy构造函数该如何实现
4. 什么是新type的“合法值”?
成员函数需要进行一些错误检查工作,这会影响到异常处理
5. 你的新type需要配合某个继承图系吗?
如果继承了某些基类,你就会受到这些基类的制约,特别是virtual函数。
如果你允许别的class继承你的class,就会影响你所声明的函数,尤其是析构函数,是否为virtual。
6. 你的新type需要什么样的类型转换?
如果允许别的类型转换为你的type,则需要添加某个以该类型为参数的构造函数。
如果允许你的type转换为其它类型,则需要重载type操作符
7. 什么样的操作符和函数对此新type而言是合理的?
成员函数
8. 什么样的标准函数应该驳回
某些函数不想让编译器自动帮你生成,将它们声明为delete
9. 谁该取用新type的成员?
定义访问权限(public、private、protected)和友元
10. 什么是新type的“未声明接口”?
它对效率、异常安全性以及资源运用提供何种保证?在这方面的保证需要在代码上添加相应的约束条件
11. 你的type有多么一般化?
其实你并非定义一个新type,而是定义一整个type家族。此时应该定义一个class template
12. 你真的需要一个新type吗?
如果只是添加一些功能,在基类上添加一两个函数可能比新加一个派生类更能达到目的
条款20:宁以“pass by const reference ”代替“pass by value”
以传“const引用”代替“值传递”可以提高效率并避免切割问题
提高效率:避免了过多的拷贝
切割问题:
函数接收一个基类,传进去的是一个派生类,并且在函数中调用虚函数。
如果是值传递,传进去的派生类会被切割,只留下基类部分。调用的虚函数会执行基类版本
如果是引用传递,传进去的就是派生类,调用的虚函数会执行派生类版本