effective C++笔记--继承与面向对象设计(二)

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

绝不重新定义继承而来的非虚函数

. 假设基类中有一个非虚的成员函数,那么派生类在公有继承基类的时候,会继承这个函数接口和它的实现,但是如果派生类中重新定义了这个函数接口,则会遮掩住基类的同名函数,这就不符合“public继承是一种is-a关系”了。所以绝对不要重新定义继承而来的non-virtual函数。

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

. 假设基类中有一个虚函数并对参数有一个缺省的值,而派生类对这个虚函数又给出了不一样的缺省值,这样做可能让你达不到想要的结果:

class B{
public:
	virtual void f(int x = 1){
		cout<<x<<endl;
	}
};

class D : public B{
public:
	void f(int x = 2){
		cout<<x<<endl;
	}
};

int main(){
	
	B* b = new D;
	b->f();
	
	return 0;
}

. 以上代码输出的结果是1.但是指针指向的是D的对象,那使用这个虚函数应该得到2才对,这是因为缺省的参数值是静态绑定(声明时确定的类型)。你也可以在派生类中提供相同的缺省值,但是这对减少代码重复就没有帮助了。

通过复合塑模出has-a或“根据某物实现出”

. 复合是类型之间的一种关系。当某种类型的对象内含其它的类型的时候,便是这种关系。比如:

class Address{...};
class PhoneNumber{...};
class Person{
public:
	...
private:
	Address addr;
	PhoneNumber pn;
};

. 可以说人有一个住址或有一个电话号码,但是不能说人是一个住址或人是一个电话号码,这就是has-a和is-a的区别。
  但是比较麻烦的是区分is-a和is-implemented-in-terms-of(根据某物实现出)这两种关系。这里假设来自己实现一个set,底层通过linked lists,但是如果直接让set继承自list的话,会出现一些问题,比如list应该允许内含有重复的元素,但set不允许含有重复的元素,由此可知,二者并不是is-a的关系,正确的做法是根据list对象来实现一个set对象:

template<class T>
class set{
public:
	bool member(const T& item) const;
	void insert(const T& item);
	void remove(const T& item);
	std::size_t size() const;
private:
	std::list<T> rep;				//用来表示set的数据
};

//实现
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>
std::size_t Set<T>::size() const{
	return rep.size();
}

. 在应用域,复合意味着has-a;在实现域,复合意味着is-implemented-in-terms-of。

明智而审慎地使用private继承

. private继承不意味着is-a关系,先看看private继承的行为:如果class之间是private继承,编译器不会自动将一个派生类对象转换成基类对象,这将造成派生类对象调用基类的成员函数时失败;另外通过private继承而来的成员属性都会变成private属性,纵使在基类中原来是public或是protected。
  private继承意味着is-implemented-in-terms-of(根据某物实现出)。这好像和复合的概念有重合,如何取舍呢?答案很简单:尽可能使用复合,必要时才使用private继承。何时才是必要?主要是当protected成员或virtual函数牵扯进来的时候。
  还有一种激进的情况也可以使用private继承来处理:当处理的class不带任何数据时。这样的class没有no-static成员变量,没有虚函数,也没有虚基类。这种所谓的empty class对象不使用任何的内存空间,因为没有数据需要存储,但是由于技术上的理由,C++决定凡是独立的对象都必须有非零大小,因此:

class Empty{};
class HasInt{
private:
	int x;
	Empty e;
};

. 这样做之后会发现sizeof(HasInt) > sizeof(int)。如果是通过继承的方式:

class HasInt : private Empty{
private:
	int x;
};

. 这样做之后会发现sizeof(HasInt) == sizeof(int)。这就是所谓的EBO(空白基类最优化),值得注意的是EBO一般只在单一继承中有效。

明智而审慎地使用多重继承

. 多重继承在C++中还是有不小的争议的。最先需要认清的事情是当使用多重继承的时候,程序可能从一个以上的基类中继承相同的名称(函数、typedef等),那可能导致更多的歧义机会,如菱形继承。
  一种针对菱形继承的可行的方式是:将最顶上的基类作为虚基类,并且令所有继承自它的class都采用virtual继承。但是采用virtual继承带来的代价是:使用virtual继承的那些class所产生的对象往往比不使用virtual继承的class产生的对象体积要大,访问虚基类成员变量时也会更耗时。对此有两条忠告:1.非必要时不要使用虚继承;2.必须使用虚基类时,尽量不在里面放数据(就像java中的接口)。
  和单一继承比较,多重继承更加复杂,使用上也更加难以理解,所以如果有一个单一继承方案和多重继承方案大约等价,那么最好使用单一继承方案。(不过没有呢?那不还是得用多重继承)

猜你喜欢

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