条款18:让接口容易被正确使用,不易被误用
shared_ptr的一个特性:不会出现”corss-DLL problem“,即对象在动态链接库(DLL)中被new创建,却在另一个DLL内被delete销毁,因为它会自动使用”每个指针专属的删除器“,当在其他DLL中时会自动追踪记录确定初始调用的那个DLL‘s delete
条款19:设计class犹如设计type
条款20:宁以pass-by-reference-to-const替换pass-by-value
pass-by-value首先会极大浪费空间及时间效率,参数都是以实参的副本为初值,调用端获得的是函数返回值的一个复件,这些复件由copy构造函数产出,且当遇到继承情况时还会出现slicing(对象切割)问题,即当derived class对象以pass-by-value传递并被视为一个base class对象,base class的copy构造函数会被调用,而derived class中的某些特性会被切割
class A{
public:
virtual void func();
};
class B:public A
{
public:
virtual void func();
};
void F(A a)
{
a.func();
}
B b;
F(b);//pass-by-value,b调用的永远都是A::func()
内置类型对象(int等)还是推荐使用pass-by-value
条款21:必须返回对象时,别妄想返回其reference
例如像operator +此类的调用,就不应该返回reference,毕竟返回的东西还没有被创建出来
PS:
不要返回pointer或reference指向一个
local stack对象,或返回reference指向一个
heap-allocated对象,或返回pointer或reference指向一个
local static对象
条款22:将成员变量声明为private
1.使用成员函数来访问内部成员
2.protected并不比public更具封装性,因此实际上只有两种访问权限:private和不封装
条款23:宁以non-member, non-friend 替换member函数
class WebBrowser
{
public:
void clearCache();
void clearHistory();
void clearBookmarks();
void clearCookies();
}
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.clearBookmarks();
wb.clearCookies();
}上述
哪种方式更好呢,答案是clearBrowser函数。
原因:
1.一个non-member, non-friend函数的封装性是要高于member函数的,因为通常来说,
越多数据可以访问它,数据的封装性就越低,member函数可以访问class内的private成员、private函数、enums、typedef等,而相比之下,non-member, non-friend函数能够访问的内容就少了很多,因此non-member, non-friend 封装性强于member函数
条款24:若所有参数皆需类型转换,请为此采用non-member函数
例如operator *,那么若遇到2*class对象时,由于2没有重载乘号运算符,因此结果错误,所以需要将operator *变成non-member函数
条款25:考虑写出一个不抛异常的swap函数
pimpl手法(pointer to implementation):以指针指向一个对象,内含真正的数据
class WidgetImpl
{
public:
...
private:
int a,b,c;
vector<double> v;
}
class Widget
{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
...
}
private:
WidgetImpl* pImpl;
}
条款26:尽可能延后变量定义式的出现时间
string s;
if(....)
{
....
}
....
return s;
上图中若if语句处出现异常,语句中断,那么s变量声明就无意义,因此应放在if语句后声明
条款27:尽量少做转型动作
1.const_cast
将对象的常量性转除
2.dynamic_cast
多用于继承对象之间
3.reinterpret_cast
执行低级转换,实际结果取决于编译器,因此不可移植
4.static_cast
强迫隐式转换
许多程序员相信,转型其实什么也没做,只是告诉编译器把某种类型视为另外一种类型,这是错误的观念。任何一个类型转换(无论是通过编译器完成的隐式还是通过转换操作完成的显式转换)往往真的令编译器编译出运行期间执行的代码。具体例子见下:
class Base{...};
class Derived : public Base
{...}
Derived d;
Base* pb = &d;
pb和d的指针值并不相同,这时会有一个偏移量在运行期间被施行于Derived*指针身上,用以取得正确的Base*指针值。
本例也表明单一对象(如Derived*)可能拥有一个以上的地址(如Base*指向它时的地址与Derived*指向它时的)
条款28:避免返回handles指向对象内部成分
class Point
{
public:
Point(int x, int y);
void setX(int newVal);
void setY(int newVal);
};
struct RectData
{
Point ulhc;
Point lrhc;
};
class Rectangle
{
public:
Point& upperLeft() const { return pData->ulhc; }
Point& lowerRight() const {return pData->lrhc;}
private:
shared_ptr<RectData> pData;
}
上述代码可通过编译,但实际上却是错的,自我矛盾。upperLeft和lowerRight为const函数 ,返回的是reference,调用者可以通过这些reference更改内部数据。
条款29:为异常安全而努力是值得的
异常安全函数提供的保证:
1.基本承诺
如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构遭到破坏,所有对象都处于一种内部前后一致的状态
2.强烈保证
如果异常被抛出,程序状态不改变
3.不抛掷保证
承诺绝不抛出异常
条款30:透彻了解inlining的里里外外
过度热衷inline函数会造成程序体积过大
一个表面上看似inline的函数是否真是inline,取决于你的建制环境,主要取决于编译器
条款31:将文件间的编译依存关系降到最低
设计策略:
1.如果使用object reference或object pointer可以完成任务,就不要使用object
2.如果能够,尽量以class声明式替换class定义式
3.为声明式和定义式提供不同的头文件
编译器必须在编译期间知道对象的大小
int x;
Person p(params);
当编译器看到时,必须知道要分配多少内存来维持x和p,特别是p,编译器需要知道需要分配多少内存来放置一个Person对象,那么唯一方法就是询问class Person的定义式
想要不列出class定义式的方法是通过指针,因为指针大小是固定不变的,具体如下
class PersonImpl;
class Date;
class Address;
class Person
{
public:
...
private:
//将对象实现细目隐藏在指针后
shared_ptr<PersonImpl> p;
};
这样一来,Person就与Date、Address的实现细目分离了,这种分离的关键在于以”声明的依存性“替换”定义的依存性“。
PersonImpl这类class被称为
handle class,还有一种就是定义一个abstract class来只定义接口,被称作
interface class
handle class和interface class的优劣: