Effective C++ 读书笔记(2)

条款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的优劣:


猜你喜欢

转载自blog.csdn.net/sinat_25394043/article/details/80105172