Effective C++ 读书笔记6

4 设计与声明


条款18:让接口容易被正确使用,不易被误用


条款19:设计class犹如设计type


条款20:宁以pass-by-reference-to-const替换pass-by-value

by value方式比by reference方式成本大,因为会调用类的拷贝构造函数,销毁时会调用析构函数,如果类内还有类,对应的类的拷贝构造函数和析构函数都会被调用,所以by value方式成本非常大。

另外,by value方式还可能造成对象的切割问题:当一个派生类对象以by value方式传递并被视为一个基类对象时,基类的拷贝构造函数会被调用,而“造成此对象的行为像个派生类对象”的那些特化性质会被切割掉,仅剩下一个基类对象。

class Window{
public:
	std::string name() const;
	virtual void display() const;
};

class WindowWithScrollBars: public Window{
public:
	virtual void display() const;
};

现在假设希望写个函数打印窗口名称,然后显示该窗口。下面是错误示范:

void printNameAndDisplay(Window w){
	
	std::cout << w.name();
	w.display();
}

当给上述函数传递一个WindowWithScrollBars对象,上述函数内调用display调用的总是Window::display。

解决切割问题的办法,就是以by reference-to-const的方式传递w。


如果窥视C++编译器的底层,reference往往以指针实现出来,因此pass by reference通常意味真正传递的是指针。因此如果你有一个对象属于内置类型,pass by value往往比pass by reference效率高些。对于内置类型而言,当你有机会采用pass-by-value或pass-by-reference-to-const时,选择pass-by-value并非没有道理。这个忠告也适用于STL的迭代器和函数对象,因为习惯上他们被设计为passed by value。(??????????)

一般而言,你可以合理假设pass-by-value并不昂贵的唯一对象就是内置类型和STL的迭代器和函数对象。至于其他任何东西都请遵守本条款的忠告,尽量以pass-by-reference-to-const替换pass-by-value。

请记住:

1.尽量以pass-by-reference-to-const替换pass-by-value.

2.以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对他们而言,pass-by-value往往比较适当。


条款21 必须返回对象时,别妄想返回期reference

有可能传递一些references指向其实并不存在的对象:

class Rational{
	
public:
	Rational(int numerator = 0, int denominator = 1);
	
private:
	int n, d;
	friend const Rational operator* (const Rational& lhs, const Rational& rhs);
};

如果operator*的实现内返回的如果是一个stack对象,那么当函数返回时该对象根本就不存在;如果返回的是一个heap对象,那么就会存在忘记delete的风险,因为:

Rationl w, x, y, z;
w = x * y * z;

这里,同一个语句内调用了两次operator*, 因而两次使用new,也就需要两次delete。但却没有合理的办法让operator*使用者进行那些delete调用,因为没有合理的办法让他们取得operator*反悔的reference背后隐藏的那个指针。有人说可以使用static Rational对象:

const Rationl& operator* (const Rational& lhs, const Rational& rhs){
	
	static Rational result;
	result = ...;
	return result;
}
就像所有用上static对象的设计一样,这一个也立刻造成我们对多线程安全性的疑虑。而且想想更深层次的瑕疵,考虑下面这些完全合理的客户代码:

bool operator== (const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
if((a*b) == (c * d)){
	
}else{
	
}

等价形式:
if(operator==(operator*(a, b), operator*(c, d)))

operator==被要求将operator*内部定义的static Rational对象值拿来和operator*内的static Rational对象值 比较,如果不相等那才怪呢。


一个必须返回新对象的函数的正确写法是:就让函数返回一个新对象:

inline const Rational operator* (const Rational& lhs, const Rational& rhs){
	
	return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

请记住:

绝对不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或者reference指向一个local static对象而有可能同时需要多个这样的对象。


条款22: 将成员变量声明为private

将成员变量声明为private的好处:

1.不用去考虑要不要加小括号,如果都是private,那么访问变量肯定都需要加括号(语法一致性)

2.使用函数可以让你对成员变量的处理有更精确的控制:

class AccessLevels{
	
public:
	int getReadOnly() const {return readOnly;}
	void setReadOnly(int value) {readWrite = value;}
	int getReadWrite() const {return readWrite;}
	void setWriteOnly(int value) {writeOnly = value;}
private:
	int noAccess;
	int readOnly;
	int readWrite;
	int writeOnly;
};

3.封装:

class SpeedDataCollection{
	
public:
	void addValue(int speed);
	double averageSoFar() const;
};

成员函数averageSoFar的设计:做法一是在类内设计一个成员变量,记录至今以来所有速度的平均值,当函数被调用时,只需返回那个成员变量就好。这样做使得函数的反应速度更快,因为不再需要计算,但会使得对象变大,因为需要给很多计算速度相关的成员分配空间。

做法二是在函数调用时才计算,这样函数的反应速度会变慢,但是对象可以做的比较小。

两种不同的做法,需要把成员变量隐藏在函数接口的背后,可以为“所有可能实现”提供弹性。


请记住:

1.切记将成员变量声明为private。这可赋予客户访问数据的一致性,可细微划分访问控制,允许约束条件获得保证,并提供class作者以充实的实现弹性。

2.protected并不比public更具封装性(???????)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值