Effective C++系列文章:
Effective C++ 1自己习惯C++ 条款01-条款04
Effective C++ 2 构造/析构/赋值运算 条款5-条款12
Effective C++ 3 资源管理 条款13-条款17
Effective C++ 4 设计与声明 条款18-条款25
Effective C++ 5 实现 条款26-条款31
Effective C++ 6 继承与面向对象设计 条款32-条款40
Effective C++ 7 模板与泛型编程 条款41-条款52
Effective C++ 8 杂项讨论 条款53-条款55
条款32 确定你的public继承塑膜出is-a关系
存在于classes之间的关系:
is-a、has-a、is-implemented-in-terms-of
条款33 避免遮掩继承而来的名称
class Father{
public:
void doSomething(){
cout<<"Father do something" << endl;
}
void doSomething(int a){
cout<<"Father do something" << a <<endl;
}
};
class Son: public Father{
public:
void doSomething(){
cout<<"Son do something"<<endl;
}
};
int main(){
Son s;
s.doSomething();//调用Son::doSomething();
s.doSomething(0);//错误!!!继承类的掩盖了父类的,不适用于重载规则
}
继承类的函数或者变量的名称会遮盖基类的同样名称的函数或者变量(尽管看起来是重载的规则)
- **公有继承:**想要调用父类的重载函数,必须为原本会被遮掩的每个名称引入一个using声明式,using声明式会令继承而来的某给定名称之所有同名函数在继承类中都可见。
class Son: public Father{
public:
using Father::doSomething;//使用using声明,注意只使用using名称。
void doSomething(){
cout<<"Son do something"<<endl;
}
};
```cpp
Son s;
s.doSomething();//打印结果“Son do something"
s.doSomething(0);//可以打印,打印结果”Father do something 0"
using关键字,打破了继承方式的限制,即如果是
class Son: private Father{
...
using Father::doSomething;
}
Son对象仍然可以调用父类的doSomething()函数。
2. 私有继承: 在私有继承之下,我们只想使用基类的某一个重载函数版本(如无参的doSomething()),using在这里就派不上用场了,我们需要不同的技术,即一个简单的转交函数(forwarding function):
class Base {
public:
virtual void mf1() = 0;
virtual void mf1(int);
... //与前同
};
class Dervied: private Base {
public:
virtual void mf1() { //转交函数,暗自成为inline
Base::mf1();//不懂这是个什么操作
}
};
Dervied d;
int x;
d.mf1(); //调用派生类的mf1
d.mf1(x); //错误,基类mf1被遮掩了
inline转交函数的另一个作用是为那些不支持using 声明式(注:这并非正确行为)的老旧编译器另辟一条新路,将继承而来的名称汇入派生类的作用域内。
条款34 区分接口继承和实现继承
表面上直截了当的public继承概念,由两部分组成:函数接口继承和函数实现继承。
纯虚函数:继承接口
非纯虚函数:继承接口和一份缺省实现
非虚函数:继承接口和一份强制实现
条款35 考虑virtual函数以外的其他选择
看不太懂
条款36 不重新定义继承而来的non-virtual函数
class Father{
public:
void doSomething(){}
};
class Son: public Father{
public:
void doSomething(){}
};
...
Father * f = new Son();
f->doSomething();//调用Father::doSomething()
Son * s = new Son();
s->doSomething();//调用Son::doSomething()
表现出谁的行为取决于指向该对象的指针类型。
如果Father::doSomething()为virtual,动态绑定,则调用的都是Son::doSomething()。
条款37 不重定义继承而来的缺省参数值
本条局限于“继承一个带有缺省参数值的virtual函数”
本条成立理由:virtual函数动态绑定,而缺省参数值是静态绑定
class Shape{
public:
enum ShapeColor{Red,Green,Blue};
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle: public Shape{
public:
virtual void draw(ShapeColor color = Green) const;//缺省值不同,不好的写法,为了解释本条,牺牲一下
}
class Circle: public Shape{
public:
virtual void draw(ShapeColor color) const;
//注意:如果客户以对象调用这个函数,一定要指定参数值
//因为静态绑定下这个函数并不从其基类继承缺省参数值
//但是如果以指针(或引用)调用此函数,则可以不指定参数值
//因为动态绑定下这个函数会从其base继承缺省参数值
}
如果使用
Circle c;
c.draw();
则报错,因为对象不支持动态绑定。
补充说明上述注意原因:**引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。**用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。
Shape * ps; //静态类型为Shape*,没动态类型
Shape * pc = new Circle; //静态类型为Shape*,动态类型是Circle
Shape * pr = new Rectangle; //静态类型为Shape*,动态类型是Rectangle
ps pc pr都被声明为指向Shape的指针,动态类型是指“目前所指对象的类型”
pc->draw(Shape::Red);//调用Circle::draw(Shape::Red)
pr->draw(Shape::Red);//调用Rectangle::draw(Shape::Red)
pr->draw();//调用Rectangle::draw(Shape::Red)而非Rectangle::Green
解释:pr->draw()调用时候的参数是静态绑定的,所以仍然是Shape::Red。这就出现了诡异现象调用继承类的函数,却在使用基类的缺省参数值
条款38 通过复合(composition)塑膜出has-a或“根据某物实现出”(is-implemented-in-terms-of)
- has-a
class Address{};
class PhoneNumber{};
class Person {
public:
...
private:
string name; //合成成分物
Address address; //合成成分物
PhoneNumber voiceNumber; //合成成分物
PhoneNumber faxNumber;//合成成分物
};
符合了三种类四个元素。
2. is-implemented-in-terms-of
使用list实现set,但是list元素可以重复,这和set不同,如果直接使用public list继承,是不对的,所以它们不是is-a关系。
template<typename T> //将list应用于Set。是错误的做法
class Set:public list<T>{...};
但是可以通过list实现出set。关注一下insert函数,可以看到,通过insert控制set中不会出现相同的元素l
template<class T>
class Set {
public:
bool member(const T& item)const;
void insert(const T& item)const;
void remove(const T& item)const;
size_t size()const;
private:
list<T> rep;//用来表述Set的数据(复合的实现域用法)
};
template<class T>
bool Set<T>::member(const T& item)const {
return find(rep.begin(), rep.end()) != rep.end();
}
template<class T>
void Set<T>::insert(const T& item)const {
if (!member(item))
rep.push_back(item);
}
template<class T>
void Set<T>::remove(const T& iterm)const {
typename list<T>::iterator it = find(rep.begin(), rep.end(), item);//此处利用typename表明list<T>::iterator是一个嵌套从属名称
if (it != rep.end()) rep.erase(it);
}
template<class T>
size_t Set<T>::size() const {
return rep.size();
}
条款39 明智而谨慎的使用private继承
private继承意味着implemented-in-terms-of,如果让class D以private形式继承class B,你的用意是采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。
阻止继承类重新定义虚函数的办法
第一种方法的主要缺点是:继承类可以重写virtual函数,即使我们不可以调用它(private)
class Timer{
public:
explicit Timer(int tickFrequency);
virtual void onTick() const;
};
//避免Timer表现得像是Widget的一部分,即Widget的接口不能有onTick(),不能public继承
//第一种办法:private继承
//缺点:继承Widget的类可以重写onTick(),因为是虚函数
class Widget: private Timer{
private:
virtual void onTick() const;
};
//第二种办法:使用复用构建嵌套类,public继承onTick()
//优点:无法重写onTick()
class Widget{
private:
class WidgetTimer: public Timer{
public:
virtual void onTick() const;
};
WidgetTimer timer;
};
条款40 明智而审慎地使用多重继承
- 多重继承可能导致调用歧义。
注意到,上边两个类一个是public,一个是private,但是mp.checkOut()仍然是错的。为了解决歧义,必须指明调用谁(当然,不能调用private的那个):
mp.BorrowbleItem::checkOut();
- 钻石型多重继承
如果FIle有个成员变量filename,IOFile对两个继承变量有两种做法:
- 两个都复制(默认方法)
- 只创建一个,需要令带有此数据的class(也就是File)成为一个virtual base class,为此,需要将所有直接继承自它的classes采用“virtual继承”
使用virtual继承的问题:类的体积变大;访问虚基类的成员变量访问速度慢;虚基类的初始化责任是由继承体系中的最底层class负责。