条款十二
使用一个类来管理你的资源(即RAII,获取即初始化)
在这个类的析构函数中释放管理的指针
条款十三
可以的话,使用shared_ptr<>和unique_ptr<>(不可复制/拷贝)来代替内置指针,复制RAII类的时候应当像这两个类一样:复制管理的底层资源->shared;或阻止拷贝,或将控制权转让,不过unique是阻止了拷贝复制。
条款十四
所有RAII类都应当有获取原始资源的方法,显示的get()函数较为安全,隐式的用户类型转换(operator type())则会比较方便。
RAII(资源获取即初始化resource acquisition is initialization)保证被管理的资源会被释放。
一个类管理的指针在构造时获取资源,析构的时候则释放资源,由于外层是一个类,故可以拥有c++的自动析构调用。
条款十五
若new时使用了new type[num]则delete时需要使用delete[](没有参数)
这一点在typedef / using定义一个数组的时候尤为重要,因为可能会注意不到使用的是一个数组的名字类型,所以干脆不要用typedef/using来定义一个新的数组的名字,使用vector等容器来代替会好的多。
条款十六
用一个独立的语句来创建你的智能指针,特别是当你需要传入智能指针到一个函数的时候
函数的实参的核算是不确定的,即两个参数不一定哪一个先完成构造
f( shared_ptr<T>(new T(),fun() )
在这样一个函数中new, T(), fun()的调用顺序不一,若new分配完内存后fun被调用并抛出内存,那么new分配的内存便泄漏。而且也在shared_ptr的构造过程中抛出了异常,使得已经构造的部分无法析构了。
条款十七
设计易用但是难出错的接口
如果一个接口要求用户必须做某事,那么就是有“不正确使用”的倾向,因为用户很可能会忘记这么一件事。
条款十八
为了设计易用但是难以误用的接口需要:
“促进正确使用”:定义与内置类型行为就兼容,并且统一的接口。比如size()表示大小,并且所有这个继承体系中都用size(),功能概念上接近的也用size()
“防止误用”:创建新类型(代替值类型),限制类型上的操作(private构造、并用static函数来取得对象),束缚对象值(static返回正确的对象,即提供所有可能出现的值,而不是让用户来定义(如果够少的话))、消除用户的资源管理责任(不返回内置指针,而是定义好了删除器的智能指针,籍此来把释放资源的责任交给系统RAII)
return shared_ptr<type>(指针,删除器)
定义删除器,而不是让用户来手动调用某个删除函数
智能指针:智能指针可以定制删除器,可以解决互斥锁问题(使用引用计数,或根本就阻止了复制),防范dll问题(用户在一个dll中new创建,却在另一个dll中delete,使用智能指针会智能追踪到让引用归零的delete)
class Date {
Date(int year,int mounth,int days)
{}
};//无任何限制,用户可能输入任何值作为年月日,只能构造函数函数内进行检测
----------------------------------------------------------------------------
struct Year {
Year(int year);
};
struct Mounth {
Mounth(int mounth);
};
struct Days {
Days(int days);
};
class Date {
Date(const Year& y, const Mounth &m, const Days &d);
};//使用类型限制,用户需要使用相应值定义固定的类型
----------------------------------------------------------------------------
struct Year {
Year(int year);
};
struct Mounth {
public:
static Mounth January() { return Mounth(1); }
//以此类推,提供全部的月份
private:
Mounth(int mounth);
};
struct Days {
Days(int days);
};
//private
class Date {
Date(const Year& y, const Mounth &m, const Days &d);
};//不仅使用类型限制,并且限制相应类型可能的值,用户只能使用已有的月份
条款十九
在定义一个type之前考虑周全:
1、新定义的类型如何销毁创建
(考虑析构与构造,explicit,operator new与delete等问题)
2、对象的初始化与赋值有什么区别
(拷贝构造函数与operator=等)
3、新的type在pass by value(值传递)时应当怎么做
(比如拷贝构造delete,像iostream一样)
4、什么是新type的合法值
(比如提供static的获取值函数,或是对成员值合法的检测)
5、新的type是否需要配合某个继承体系
(类型是否受基类的限制,哪些函数为virtual,尤其是析构函数)
6、新的type需要哪些类型转换
(显示隐式转换,单一构造函数是否explicit)
7、需要的操作符与函数
(成员函数和操作符的定义)
8、什么样的标准函数应当被驳回
(不会被类外使用的函数定义成private)
9、该使用那些成员
(哪些成员为public,什么定义为友元,是否使用嵌套类)
10、什么是这个类型的“未声明接口”
11、新的类型有多么一般化
(决定你要不要把它定义成泛型,或者要不干脆用泛型函数代替,而不是声明新类)
12、你是否真的需要一个新的类型
(如果只是为了拓展基类的功能,或许使用更多的成员函数,或泛型函数会更好)
条款二十
以pass-by-reference-to-const代替pass-by-value
以常量引用代替值传递
可以减少参数的复制消耗,还有函数结束时析构函数的消耗
对于内置值类型和STL的迭代器与函数对象(并不是容器本身),可以选用pass-by-value时还是使用值传递。
条款二十一
并不是所有时候都返回引用更好
不要返回局部变量的指针、引用(大部分时候你返回local stack会得到编译器的警告);不要返回一个动态对象(heap-allocated需要你手动删除,但是鬼晓得你会不会delete);也不要在可能需要多个对象的时候返回一个local-static的pointer/reference
class num {
friend const num& operator*(const num&, const num&);
};
const num& operator*(const num& lhs, const num& rhs)
{
static num result;
//计算
return result;
}
//很显然,当你比较两个乘式的结果时出现问题
//(a * b) == (b * c)永远为true,因为返回的是static对象的引用,第二次计算后改变static
//对象的值,两个结果都绑定这个对象,故永远相等。
//很显然,当你比较两个乘式的结果时出现问题
//(a * b) == (b * c)永远为true,因为返回的是static对象的引用,第二次计算后改变static
//对象的值,两个结果都绑定这个对象,故永远相等。