effective C++笔记——设计与声明

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rest_in_peace/article/details/83652874

让接口容易被正确使用,不易被误用

. 创建一个容易被正确使用的接口,首先必须考虑客户可能犯什么样的错误,比如对函数的参数传递顺序有误,或者参数是无效的,以及返回值并不安全等,书中举了一个关于日期的函数,客户可能将月份和天数的顺序输入弄错,也可能输入无效日期如2月30日。
  对接口保持一致性的原则往往对正确使用接口有帮助,比如STL容器的接口大多都是一致的,每个STL容器都有一个名为size的成员函数,用来表明容器中当前有多少个对象,这样比一会儿使用size一会使用length的方式要好很多。

设计class犹如设计type

. c++如同其他面向对象编程语言一样,定义了一个新的class就定义了一个新的type,如何设计高效的class通常需要面对以下的问题:

  • 新type的对象应该如何被创建和删除: 这将影响class的构造函数和析构函数以及内存的分配和释放问题。
  • 对象的初始化和对象的赋值有什么区别: 这将影响到构造函数和赋值操作符的行为,以及两者的差异。
  • 新type的对象如果被pass-by-value,意味着什么?
  • 什么是新type的合法值: 这影响对数据的错误检查,函数的异常抛出等。
  • 新type是否需要配合某个继承体系: 值得注意的是析构函数是否需要声明为virtual。
  • 新type需要什么样的转换?
  • 什么样的操作符和函数对此type而言是合理的?
  • 什么样的标准函数应该被驳回: 那些应当被声明为private
  • 谁该取用新type的成员: 对权限(private、public、protected)和友元的设定是否合理
  • 什么是type的未声明接口?
  • 新的type有多一般化: 有没有必要直接做泛型处理。
  • 真的需要一个新的type吗: 如果只是为既有的class添加功能,说不定单纯的定义一个或多个non-member函数或template,更能达到目标。

宁以pass-by-reference-to-const替换pass-by-value

. 值传递对是创建参数的一个副本,这虽然不会改变传入的参数本身,但是这将带来更多更费时的操作,使用常引用(指针)的方式传值将回避这种情况。

必须返回对象是,别妄想返回其reference

. 知道了值传递带来的种种不便后,可能会坚持使用引用传递的方式,这时往往会犯一个致命错误:开始传递一些references指向其实并不存在的东西。比如一个函数的返回值是一个引用,在函数中创建了本地对象,在函数结束的时候将它返回,这个本地对象实际上是被销毁了,外部的变量取得的值实际上是不知道指向是什么地方的。
  还有就是不要让这个引用或者指针指向堆上动态分配的内存,因为这些内存是需要手动去释放的,在多次调用这个函数时,将带来很多内存管理的麻烦。

将成员变量声明为private

. 众所周知类的三大特性之一是封装,将成员变量声明为private,通过一系列函数来进行访问能很好的实现封装的效果。

宁以non-member、non-friend替换member函数

. 假设有一个类用来表示网页浏览器,其中有三个函数分别用来删除缓存、删除历史记录以及删除所有cookies:

class WebBrowser{
public:
	...
	void clearCache();
	void clearHistory();
	void removeCookies();
	...
};

假设想一整个执行所有的清除操作,很容易想到两种方法:
1.为类再添加一个方法,在这个方法中调用以上三个步骤:

void clearAll();				//调用clearCache、clearHistory和removeCookies

2.写一个non-member函数来调用以上三个步骤:

void clearAll(WebBrowser& b){
	b.clearCache();
	b.clearHistory();
	b.removeCookies();
}

. 以上两种方式哪种更好呢?根据面向对象守则要求数据应该尽可能被封装,然而member函数版本比non-member函数版本的封装性要低。因为增加一个成员函数就增加了访问类的成员变量的机会。

扫描二维码关注公众号,回复: 4555205 查看本文章

若所有的参数皆需要类型转换,请为此采用non-member函数

. 令classes支持隐式类型转换通常是个糟糕的主意,但是这个规则也有例外,比如自己创建一个有理数的类,应该支持从整型数转换到有理数的转换:

class Rational{
public:
	Rational(int numerator = 0,int denominator = 1); //不将构造函数设为explicit的,
																				//允许隐式转换
	int numerator() const;
	int denominator() const;
private:
	...
};

这个类应该要支持算术运算,例如加法、乘法等,但是应该将运算的函数写成成员函数还是非成员函数呢?先假设写成成员函数的版本:

class Rational{
public:
	...
	const Rational operator*(const Rational& rhs) const;
};

这种方式可以使用运算了,但是有一种情况却会导致错误:

Rational r1(1,8);
Rational r2(1,2);
Rational result = r1 * r2;						//正确
result = result * r1;							//正确
result = r1 * 2;							//正确
result = 2 * r1 ;							//错误

可以看出怎么连乘法连交换律都不能实现了,因为错误的那个语句和下面的形式一样:

result = 2.operator(r1 );

r1是一个内含operator成员函数的对象,所以编译器调用该函数,整数2并没有相应的class,也就没有operator成员函数。编译器也会尝试寻找非成员函数的接受两个参数(一个int,一个Rational对象)的operator*函数,但是并不存在,所以查找也会失败。
  因此让这个函数成为一个非成员函数是一个可行的方法,编译器会允许在每一个实参身上执行隐式转换,以上错误的形式就能通过编译了:

const Rational operator*(const Rational& lhs,const Rational& rhs){
	...
}
result = 2 * r1;				//正确

猜你喜欢

转载自blog.csdn.net/rest_in_peace/article/details/83652874