本章主要围绕继承展开讨论. 如何确定是 public 继承还是 private 或 protected 继承, 继承中virtual的确定等等. 信息量较大.
并不是满足 is-a 的所有关系都可以使用 public 继承, 例如 square is-a rectangle, 但是并不是每一个适用于 rectangle 的函数都适用于 square, 这种时候请更改 rectangle 使之更加泛化或者不让 square 继承 rectangle
Remeber:
“public 继承”意味着”is-a”关系, 适用于base classes身上的每一件事都适用于derived classes上
virtual 函数意味着 “接口被继承”, non-virtual 函数意味着 “接口和实现都必须被继承”
void Derived::mf4(){
...
mf2();
...
}
编译器查找函数名的规则是: 先查找local作用域, 再查找外围作用域(Derived Class), 再查找Base Class, 再到 namespace, 最后到全局作用域.
但是注意对于重载函数的继承, 有些特殊.
例如:
class Base{
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived: public Base{
public:
virtual void mf1();
void mf3();
...
};
上面这段代码, 在Derived class实现的 mf3 和 mf1 将会遮挡重载函数, 产生编译错误.
Derived d;
d.mf1(1); // 错误
d.mf3(1.0); // 错误
所以对于重载函数的继承, 需要在子类中使用 using, 或者 forwarding :
class Derived: public Base{
public:
using Base::mf1;
using Base::mf3;
virtual void mf1();
void mf3();
...
};
// or
class Derived public Base{
public:
virtual void mf1(){
Base::mf1();
}
...
};
这样就可以达到目的了.
Remeber:
为了避免子类继承不想要的缺省实现可以通过声明为 pure virtual 函数, 并定义一个 non-virtual 的 defaultImplement实现:
class Airplane{
public:
virtual void fly(const Airport& dest) = 0;
...
protected:
void defaultFly(const Airport& dest);
};
class ModelA: public Airplane{
public:
virtual void fly(const Airport& dest){
defaultFly(dest);
}
...
};
这样可以避免使用者在无意识的情况下继承缺省实现.
Remeber:
有些时候我们可以考虑一些非 virtual function 来替代 virtual function, 会有意向不到的效果.
就比如说通过函数指针实现 Strategy 模式, 下面以一个计算血量的代码为例:
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc): healthFunc(hcf)
{}
int healthValue() const
{ return healthFunc(*this); }
private:
HealthCalcFunc healthFunc; // function pointer
};
这样通过外部嵌入函数指针, 有点类似于依赖注入(IoC)和控制反转(DI). 其实也能够更方便测试(可以很好地利用 GMock 等工具)
对于上面这段代码, 还可以使用 tr1::function
来替换, tr1::function 是一个函数指针class, 可以兼容任何可调用物(callable entity, 也就是函数指针, 函数对象, 或成员函数指针).
只需要把上面的
typedef int (*HealthCalcFunc)(const GameCharacter&);
改为:
typedef std::tr1::function HealthCalcFunc;
就可以了.
Remeber:
绝不要重新定义继承而来的 non-virtual 函数.
因为对于同样的 non-virtual 函数, 按道理行为是相同的, 否则容易引起歧义. 如果行为是不相同的, 请改为 virtual
Remeber:
绝不要重新定义继承而来的 non-virtual 函数
这是一条很重要的规则, 缺省参数值都是静态绑定的, 也就是说缺省参数值根据调用者的类型而确定:
比如下面这段代码:
class Shape{
public:
virtual void print(int num = 1) const = 0;
};
class Rect: public Shape{
public:
virtual void print(int num = 2) const{
printf("%d\n", num);
}
};
int main(){
Shape *p = new Rect;
p->print();
}
实际上输出会是 1, 也就是说默认参数是采取的 Shape 里面的 num = 1, 而不是 Rect 中的 num = 2, 所以说是通过 virtual 动态调用函数, 但是却是静态绑定的参数. 各出一半力完成的这个任务.
所以最好的办法是保持默认参数的一致, 默认参数是不会继承的, 所以需要手动保持一致, 一个修改统一修改. 还一个解决问题的方法就是通过之前提到的 NVI(non-virtual interface) 的手法实现. 在public interface中设定默认参数, 在这里不赘述.
Remeber:
绝不要重新定义一个继承而来的缺省参数值, 因为缺省参数值都是静态绑定的, 而 virtual 函数却是动态绑定
Remeber:
通过复合来实现 has-a 或者 “根据某物实现出”
Private继承相对于 public 继承主要有两点不同:
Remeber:
对于多重继承, 首先是函数名容易引起歧义, 当继承的两个父类中有相同的函数名的时候, 必须指明使用的哪个 base class 的函数:
class BaseA{
public:
void check();
};
class BaseB{
public:
void check() const;
};
class Derived: public BaseA, public BaseB
{ ... };
Derived temp;
temp.check(); // 歧义
temp.BaseA::check(); // 正确
当出现”钻石型多重继承”的时候, 就是说多重继承的base classes之上还共同继承了同一个 base class, 需要使用virtual 继承来解决:
class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile, OutputFile { ... };
所以说:
Remeber:
Effective C++ (1): Accustoming Yourself to C++
Effective C++ (2): Constructors, Destructors, and Assignment Operators
Effective C++ (3): Resource Management
Effective C++ (4): Designs and Declaration
Effective C++ (5): Implementation
Effective C++ (6): Inheritance and Oject-Oritent Design
Effective C++ (7): Templates and Generic Programming
Effective C++ (8): Customizing new and delete
Effective C++ (9): Miscellany