条款13:以对象管理资源
使用RAII,获得资源后立刻放进管理对象,管理对象运用析构函数确保资源被释放。
为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
两个常被使用的RAII类是tr1::shared_ptr和tr1::auto_ptr。前者通常是较佳选择,因为其拷贝行为比较直观。若选择auto_ptr,复制动作会使被复制物指向null.
条款14:在资源管理类中小心拷贝行为
n 复制RAII对象必须一并复制它所管理的资源,所以资源拷贝行为决定RAII对象的拷贝行为。
n 普遍而常见的RAII类拷贝行为是:抑制拷贝,使用引用计数法。
条款15:在资源管理类中提供对原始资源的访问
n APIs往往要求访问原始资源,所以每一个RAII类应该提供一个取得其所管理之资源的办法
n 对原始资源的访问可能经由显示转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。
条款16:成对使用new和delete时要采取相同形式
考虑下面的typedef,:
typedef std::string AddressLines[4];
由于AddressLines是个数组,如果这样使用new;
std::string *pal=new AddressLInes;
那就必须匹配数组形式的delete:
delete pal; //未定义行为
delete []pal; //正确
n 如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。
条款17:以独立语句将newed对象置入智能指针
考虑以下代码:
int priority(); //处理程序的优先级
void processWidget(std::tr1::shared_ptr<Widget> pw,int priority);
processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());
以上代码可以通过编译,但却不是异常安全的,因为new Widget和priority()的调用顺序是不确定的。如果调用的顺序是这样的:
1. 执行new Widget
2. 调用priority
3. 调用tr1::shared_ptr构造函数
如果调用priority时出现了异常,new Widget申请的资源将会出现内存泄露。
避免上述问题的方法是分离语句:
std::tr1:shared_ptr<Widget> pw(newWidget);
processWidget(pw,priority());
这样,编译器就不能任意选择语句的执行顺序了。
n 以独立语句将newed的对象存储到智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。
条款18:让接口容易被正确使用,不易被误用
n 好的接口容易被正确使用,不容易被误用,你应该在你的所有接口中努力达成这些性质。
n 促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容。
n 阻止误用的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
条款19:设计class犹如设计类型
设计优秀的calsses是一项艰巨的工作,因为设计好的types是一项艰巨的工作。好的types有自然的语法,直观的语义,以及一个或者多个高效实现品。在C++中,一个不良规划下的class定义恐怕无法达到上述任何一个目标,甚至class的成员函数的效率有可能受到他们如何被声明的影响。
条款20:宁以传递指向const的引用代替传值
考虑以下类:
classWindow
{
public:
std::string name()
const;
virtualvoid display()const;
};
classWindowWithScrollBars:public Window
{
public:
virtualvoid display()const;
};
voidprintNameAndDisplay(Window w)
{
std::cout<<w.name()<<endl;
w.display();
}
如果调用上述函数并交给它一个Window对象,在函数内调用的是Window::display,而不会调用派生类的display函数,解决上述问题的办法是:
void printNameAndDisplay(const Window&w)
{
std::cout<<w.name();
w.display();
}
传引用的方式相比于传值的方式,有两个优点,一是提高程序的执行效率二是避免了对象的分割问题。
n 尽量以传递指向常量的引用代替传值,前者通常更加高效,并可避免切割问题
n 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,传值往往比较适当。
条款21:必须返回对象时,别妄想返回其引用
如果重载乘法运算符如下:
const Rational & operator *(constRational &lhs,const Rational &rhs)
{
Rational result(lhs.n*rhs.n,lhs.d*rhs.d);
return result;
}
这个函数返回了局部变量的引用,因而是错误的。
在看另外一个版本:
const Rational & operator *(constRational& lhs,const Rational & rhs)
{
Rational *result=newRational(lhs.n*rhs.n,lhs.d*rhs.d);
return *result;
}
这个函数完成了对象的初始化工作,但是堆上分配的内存无法释放因为会造成内存泄露问题。
必须返回一个对象时,函数的正确写法是:就让那个函数返回一个新对象。
函数可以改写如下:
inline const Rational operator *(constRational &lhs,const Rational &rhs)
{
return Rational(lhs.n*rhs.n,lhs.d*rhs.d);
}
为了获取正确的结果,必须忍受一个构造函数和析构函数的代价。
条款22:将成员变量声明为private
n 切记将成员变量声明为private,这可赋予客户访问数据的一致性,可细微划分访问控制,允诺约束条件获得保证,并提供class作者以充分的实现弹性。
条款23:宁以non-member non-friend函数替换member函数
考虑以下类:
classWebBrower
{
public:
voidclearCache();
voidclearHistory();
voidremoveCookies();
};
如果想要连续调用三个函数,可以这样设计一个成员函数:
classWebBrowser
{
public:
voidclearEverything(); //调用上面三个函数
};
这个函数也可以使用以下的版本代替:
voidclearBrowser(WebBrowser & wb)
{
wb.clearCache();
wb.clearHisttory();
wb.removeCookies();
}
这个非成员非友元的版本要比成员函数的版本更好,因为它不能访问private成员,提供了更大的封装性。
条款24:若所有参数皆需类型转换,请为此采用非成员函数
考虑以下类的定义:
class Rational
{
public:
constRational operator *(const Rational *rhs) const;
…
}
尝试如下方式调用:
result=oneHalf*2; //正确
result=2*oneHalf; //错误
只有参数被列于参数列表中时,这个参数才是隐式类型转换的合格参与者。
n 如果想让result=2*oneHalf;也成为合法的调用,可以把operator*()函数写成非成员函数的形式。
如果需要为某个函数的所有参数进行类型转换,那么这个函数必须是个非成员函数。
条款25:考虑写出一个不抛出异常的swap函数
n 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
n 如果你提供一个memberswap,也该提供一个non-member swap用来调用前者,对于类也请特化std::swap