条款13
以对象管理资源
//使用auto_ptr或者shared_ptr管理资源
class Investment{...}
...
Investment* createInvestment();
...
void f()
{
Investment* pInv = createInvestment();
...
delete pInv; //容易忘记delet出现内存泄露
}
...
//以下是改进
void f()
{
auto_ptr<Investment> pInv(createInvestment());
...
//在指针被销毁时同时销毁内存占用
}
...
auto_ptr<Investment> pInv1(createInvestment);
auto_ptr<Investment> pInv2 = pInv1;
//以上情况下将 pInv1赋值给pInv2之后,pInv1被设置成NULL,内存交给pInv2管理,即删除时机决定于pInv2
//改进方法如下
void f()
{
shared_ptr<Investment> pInv1(createInvestment());
shared_ptr<Investment> pInv2 = pInv1;
...
//以上写法只有在pInv1和pInv2都被销毁时才销毁内存数据
//采用引用计数型智慧指针
}
条款14
在资源管理类中小心coping行为
//涉及多线程,此处不讨论
条款15
在资源管理类中提供对原始资源的访问
shared_ptr<Investment> pInv(createInvestment()); //此时pInv是shared_ptr<Investment>类型变量
Investment* p = pInv.get(); 将pInv转化成Investment*类型的方法---调用get方法
//shared_ptr<>重载了->和*运算符来直接访问原始资源数据
...
class Font
{
public:
explicit Font(FontHandle fh):f(fh){}
~Font(){...}
private:
FontHandle f;
}
//以上类若在使用时要访问FontHandle必须添加一个get类
...
FontHandle get()const{return f;}
//但是每次都调用get是不方便的
//解决方法
class Font{
public:
...
operator FontHandle()const{return f;}
...
}
//以上方法在将Font赋值给FontHandle类型变量时会自动转换类型成FontHandle类型
...
条款16
成对使用new和delete时要采用相同形式
string * stringArray = new string[100];
delete stringArray //只删除了第一个元素
...
delete[] stringArray; //删除所有元素
//先读取一长串的内存空间解析成string类型,不管是不是属于string[100]之内,挨个调用构造函数进行销毁
...
条款17
以独立语句将new过的对象置入智能指针
int priority();
void processWidget(shared_ptr<Widget> pw, int priority);
...
processWidget(shared_ptr<Widget>(new Widget), priority());
//以上语句做了三件事
//执行new Widget
//调用priority
//调用shared_ptr
//以上三者的顺序未定,如果在调用priority的过程中抛出异常,会导致new Widget返回的指针丢失,因为还未被放入shared_ptr内,导致内存泄漏
//解决方法---分两行写
shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
条款18
让接口容易被正确使用,不易被误用
//关键点---shared_ptr允许当智能指针被建立起来时指定一个删除器deleter
shared_ptr<Investment> pInv(static_cast<Investment*>(0), getRidOfInvestment);
//建立一个null的shared_ptr并自定义删除其getRidOfInvestment
...
//shared_ptr会自动使用它的每个指针专属的删除其进行删除
//消除了cross-DLL-problem的隐患
//即在一个DLL中创建的变量在另一个DLL中delete,此时由于处在不同的堆,"delete"是不一样的,所以会导致错误
//而shared_ptr会自动跟踪到指针创建的那个DLL的堆里的delete进行删除
条款19
设计class犹如设计type
//一下几点注意事项
//1.新type的对象应该如何被创建和销毁
//2.对象的初始化和对象的赋值有什么样的差别
//3.新type的对象如果被pass-by-value意味着什么
//4.什么是新type的合法值
//5.你的新type需要配合某个继承图系吗
//6.你的新type需要什么样的转换
//7.什么样的操作符和函数堆此新type而言是合理的
//8.什么样的标准函数该驳回(哪些是private)
//9.谁该取用新type成员(哪些变量是public哪些是private)
//10.什么事新type的未声明接口
//11.你的新type有多一般化
条款20
宁以pass-by-reference-to-const替换pass-by-value
class Person{
...
private:
string schoolName;
string schoolAddress;
...
}
class Student:public Person{}
...
bool validateStudent(Student s);
Student plato;
...
validateStudent(plato);
//此步执行的操作有
//调用形参s的父类Person的复制构造函数
//调用形参s的类Student的复制构造函数
//调用string的复制构造函数
//优化方案
bool validateStudent(const Student& s); //const为了让函数内部无法改变参数的值
//此方案未调用任何构造函数,仅仅将plato取了别名
//传引用的方法同时解决了对象切割问题:如以下
class Window{
...
virtual void display()const;
}
class Something:public Window{
virtual void display()const;
}
...
void print(Window w);
...
Something s;
print(s);
//上述例子中将s传递给w之后形参w只会执行Window类的复制构造函数将s的父类成分复制
//解决方法
void print(const Window& w);
条款21
必须返回对象时,别妄想返回其reference
const Rational& operator* (const Rational& lhs, const Rational& rhs)
{
...
Rational reasult(...);
return reasult;
}
//由于reasult是局部变量,在return后被销毁,返回的引用指向了一个不存在的对象
const Rational& operator*(同上)
{
static Rational reasult;
...
return reasult;
}
//以上写法在遇到如下情况时出错
if((a*b) == (c*d)){...}
//可以发现if内部的表达式始终为true因为static变量的空间只有一个,始终进行判断的都是当前static变量内容
条款22
将成员变量声明为private
不解释
条款23
宁以non-member,non-friend替换member函数
class Web
{
public:
...
void doSomething();
...
}
...
void doSomething(Web& b){...}
//通常用以上方法代替前面的member方法
//若要访问Web中的private变量只需在类中声明doSomething为friend
//通常将Web与doSomething封装在一个namespace中
namespace WebStuff
{
class Web{...}
void doSomething(Web& b){...}
}
条款24
若所有参数皆需类型转换,请为此采用non-member函数
class Rationel{
...
public:
const Rational operator*(const Rationel& rhs)const;
...
};
Rational a,b;
a = b*2; //成功
a = 2*b; //失败 因为2不是类,没有重载的运算符,无法接收Rational类型的变量
//解决方法 写成non-member函数
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
...
}
//以上函数通常放在类的public里而不是friend,原因是为了保证封装性
条款25
考虑写一个不抛出异常的swap函数
namespace std{
template<typename T>
void swap(T& a,T& b)
{
T temp(a);
a = b;
b = temp;
}
}
...
class Widget
{
...
private:
Widget* p;
}
Widget a(...);
Widget b(...);
swap<Widget>(a,b);
//若采取以上方法,会复制大量没用的数据,但是需要的仅仅是private里的p
//解决方法
namespace std
{
template<> //特例化
void swap<Widget>(Widget& a,Widget& b)
{
swap(a.p,b.p); //仅交换p指针
}
}
//由于p是private,所以编译不通过
//解决方法
class Widget
{
public:
...
void swap(Widget& other)
{
using std::swap;
swap(p,other.p);
}
...
}
namespace std
{
template<>
void swap<Widget>(Widget& a,Widget& b)
{
a.swap(b);
}
}
...
...
namespace std
{
template<typename T>
void swap<Widget<T>>(Widget& a,Widget& b)
{
a.swap(b);
}
}
//以上写法是错误的
...
template<typename T>
void doSomething(T&a,T& b)
{
using std::swap; //若写成std::swap(a,b)则调用的是std里的swap而不是挑选最适合的swap
//类似
//using a;
//using b;
//这样调用几个命名空间,调用的时候就自动选择最合适的
...
swap(a,b);
...
}