【effective c++读书笔记】【第6章】继承与面向对象设计(3)

条款36:绝不重新定义继承而来的non-virtual函数

1、例子

#include<iostream>
using namespace std;

class Base{
public:
	void mf(){ cout << "Base::mf()" << endl; }
};
class Derived :public Base{
public:
	void mf(){ cout << "Derived::mf()" << endl; }
};

int main(){
	Derived d;
	Base* pb = &d;
	pb->mf();
	Derived* pd = &d;
	pd->mf();

	system("pause");
	return 0;
}
运行结果:

对于同一个对象d,使用不同类型的指针指向它时,表现不同。上面main函数中pb->mf();调用是Base类的mf函数,pd->mf();调用的是Derived类的mf函数,原因是非虚成员函数如Base::mf和Derived::mf都是静态绑定,由于pb被声明为一个Base类的指针,通过pb调用的非虚成员函数永远是B所定义的版本,即使pb指向一个类型为“Base类派生的类”的对象。

条款32说public继承意味is-a的关系,适用于Base对象的每一件事,也适用于Derived对象,因为每个Derived对象都是一个Base对象,条款34说在类内声明一个非虚函数会为该类建立起一个不变性凌驾其特异性,Base的继承类一定会继承Base类的非虚成员函数的接口和实现。现在Derive类重新定义mf,违反上面两条准则。因此任何情况下都不该重新定义一个继承而来的非虚成员函数。

请记住:

  • 绝对不要重新定义继承而来的non-virtual函数。

条款37:绝不重新定义继承而来的缺省参数值

1、对象的所谓静态类型就是它在程序中被声明时所采用的类型,动态类型则是指“目前所指对象的类型”。

例子:

#include<iostream>
#include<string>
using namespace std;

class Shape{
public:
	virtual void draw(const string& color = "red") const = 0;
};
class Rectangle :public Shape{
public:
	//注意,赋予不同的缺省参数值,这真糟糕
	virtual void draw(const string& color = "green") const{
		cout << color << endl;
	}
};
class Circle :public Shape{
public:
	virtual void draw(const string& color) const{
		cout << color << endl;
	}
	//以上这么写当客户以对象调用此函数,一定要指定参数值 。  
	//因为静态绑定下这个函数并不从其base继承缺省参数值。   
	//但若以指针(或reference)调用此函数,可以不指定参数值,   
	//因为动态绑定下这个函数会从其base继承缺省参数值。  
};

int main(){
	Shape* pr = new Rectangle;
	pr->draw();

	Shape* pc = new Circle;
	pc->draw(); //可以不指定参数值,因为动态绑定下这个函数会从其base继承缺省参数值 

	Circle c;
	c.draw("black"); //一定要指定参数值,因为静态绑定下这个函数并不从其base继承缺省参数值 

	system("pause");
	return 0;
}

运行结果:


上述例子中,pr,pc都被声明为pointer-to-Shape类型,所以他们都以Shape*为静态类型,而pr的动态类型为Rectangle*,pc的动态类型为Circle*。c的静态类型和动态类型都是Circle。

2、virtua函数是动态绑定,意思是调用一个virtual函数时,究竟调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型,而缺省参数值却是静态绑定。因此可能会出现调用一个定义于继承类内的虚函数的同时,却使用了基类为它所指定的缺省参数值。上述例子pr->draw();便是这种情况。情况不局限于pr是指针的情况,当把指针换成引用问题仍然存在。c++使用这种方式的原因是运行期效率。

3、如果遵守这条规则,并且同时提供缺省参数值给基类和继承类的用户,又带来了其他的问题。首先是代码重复,更糟的是,代码重复又带着相依性。如果基类的默认参数值改变了,那么所有派生类的默认参数也必须改变。一种较好的方法是条款35提到的NVI手法:

#include<iostream>
#include<string>
using namespace std;

class Shape{
public:
	void draw(const string& color = "red") const{
		doDraw(color);
	}
private:
	virtual void doDraw(const string& color) const = 0;
};
class Rectangle :public Shape{
public:
	virtual void doDraw(const string& color) const{
		cout << color << endl;
	}
};
class Circle :public Shape{
public:
	virtual void doDraw(const string& color) const{
		cout << color << endl;
	}
};

int main(){
	Shape* pr = new Rectangle;
	pr->draw();

	Shape* pc = new Circle;
	pc->draw();

	Circle c;
	c.draw("black");

	system("pause");
	return 0;
}

这个设计使得draw函数的color缺省参数值总是red。

请记住:

  • 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。

条款38:根据复合塑模has-a或“根据某物实现出”

1、某种类型对象内含其他类型对象是一种复合关系。复合意味has-a或is-implement-in-terms-of。程序中塑造的世界中的某些事物,如人,汽车等,这样的对象属于应用域部分。其他对象则是实现细节上的人工制品,像是缓冲区、互斥器,查找树等。这些对象相当于软件的实现域。has-a关系发生于复合发生于应用域内的对象间,is-implement-interm-of关系发生于实现域内。
2、比较难区分的是is-a和is-implement-in-terms-of这两种对象关系。
例子:
template<typename T> //将list应用于Set,错误做法
class Set :public std::list<T>{...};
上述list内的元素可以重复,而Set却不能,因此不是一种is-a的关系,而是Set可以用list实现出来。
正确做法:
//Set.h
#ifndef SET_H
#define SET_H

#include<list>

template<typename T>
class Set{
public:
	bool member(const T& item)const;
	void insert(const T& item);
	void remove(const T& item);
	size_t size()const;
	void print();
private:
	std::list<T> rep;
};
template<typename T>
bool Set<T>::member(const T& item)const{
	return std::find(rep.begin(), rep.end(), item) != rep.end();
}
template<typename T>
void Set<T>::insert(const T& item){
	if (!member(item))
		rep.push_back(item);
}
template<typename T>
void Set<T>::remove(const T& item){
	typename std::list<T>::iterator it = std::find(rep.begin(), rep.end(), item);
	if (it != rep.end())
		rep.erase(it);
}
template<typename T>
size_t Set<T>::size()const{
	return rep.size();
}
template<typename T>
void Set<T>::print(){
	list<T>::iterator it = rep.begin();
	for (; it != rep.end(); ++it)
		cout << *it << " ";
	cout << endl;
}

#endif
//main.cpp
#include"Set.h"
#include<iostream>
using namespace std;

int main(){
	Set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	s.insert(4);
	s.insert(5);

	cout << s.size() << endl;
	s.print();
	cout << boolalpha << s.member(4) << endl;

	s.remove(3);
	s.print();

	system("pause");
	return 0;
}
为了更好的说明本条款,这个Set没有遵循STL容器的协议,如果要遵循,需要添加很多东西,会模糊它和list之间的关系。
请记住:
   
   
  • 复合(composition)的意义和public继承完全不同。
  • 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值