Effective C++笔记: 继承和面向对象设计(一)
2009-07-17 17:57
351 查看
Item 32: 确保你的public inheritance 塑模出 "is-a"关系
如果你写了一个 class D ("Derived") 从 class B ("Base") 公开继承,你就是在告诉 C++ 编译器(以及你的代码的读者)每一个类型为 D 的对象也是一个类型为 B 的对象,但是反之则不然。你就是在说 B 描绘了一个比 D 更一般的概念,D 描述了一个比 B 更特殊的概念。你就是在声称一个类型为 B 的对象可以使用的任何地方,一个类型为 D 的对象一样可以使用,因为每一个类型为 D 的对象也就是一个类型为 B 的对象。另一方面,如果你需要一个类型为 D 的对象,一个类型为 B 的对象则不行:每一个 D 都是一个 B,但是反之则不然。
public inheritance 断言,适用于 base class objects(基类对象)的每一件事——每一件事!——也适用于 derived class objects(派生类对象)。在我们考虑继承关系的时候,必须考虑这一点。比较典型的是矩形和正方形,通常我们会直觉的认为正方形应该从矩形继承。但问题出来了:
考虑如下代码:
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const; // return current values
virtual int width() const;
...
};
void makeBigger(Rectangle& r) // function to increase r's area
{
int oldHeight = r.height();
r.setWidth(r.width() + 10); // add 10 to r's width
assert(r.height() == oldHeight); // assert that r's
} // height is unchanged
很清楚,断言应该永远不会失败。makeBigger 仅仅改变了 r 的宽度,它的高度始终没有变化。
现在,考虑以下代码,使用 public inheritance 使得 squares 可以像 rectangles 一样进行处理:
class Square: public Rectangle {...};
Square s;
...
assert(s.width() == s.height()); // this must be true for all squares
makeBigger(s); // by inheritance, s is-a Rectangle,
// so we can increase its area
assert(s.width() == s.height()); // this must still be true
// for all squares
和刚才那个一样明显,第二个断言也应该永远不会失败。根据定义,正方形的宽度和高度是相等的。
现在,如果在square的实例上调用setWidth或setHeight,就出现问题了。
总结:
在确定public继承之前,请确定适用于base class的每一件事也一定适用于derived class,因为每一个derived class对象也都是一个base class对象。
Item 33: 避免覆盖(hiding)通过继承而来的名称
编译器的搜索顺序:假设有以下代码
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
...
};
class Derived: public Base {
public:
virtual void mf1();
void mf4();
...
};
void Derived::mf4()
{
...
mf2();
...
}
当编译器看到一个名为mf2的使用,它就必须断定它指涉什么。它通过搜索名为 mf2 的某物的定义的作用域来做这件事。首先它在 local 作用域中搜索(也就是 mf4 的作用域),但是它没有找到被称为 mf2 的任何东西的声明。然后它搜索它的包含作用域,也就是 class Derived 的作用域。它依然没有找到叫做 mf2 的任何东西,所以它上移到它的上一层包含作用域,也就是 base class 的作用域。在那里它找到了名为 mf2 的东西,所以搜索停止。如果在 Base 中没有 mf2,搜索还会继续,首先是包含 Base 的 namespace(s)(如果有的话),最后是 global 作用域。
因此,Derived class中的同名成员会覆盖base class中的成员,包括变量或函数。对成员函数来说,被覆盖与否只与函数名相关,与函数参数,函数类型(pure virtual, virtual, non-virtual)等无关。
class Base {
private:
int x;
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();
void mf4();
...
};
base class 中的所有名为 mf1 和 mf3 的函数被 derived class 中的名为 mf1 和 mf3 的函数覆盖。从名字搜索的观点看,Base::mf1 和 Base::mf3 不再被 Derived 继承!
Derived d;
int x;
...
d.mf1(); // fine, calls Derived::mf1
d.mf1(x); // error! Derived::mf1 hides Base::mf1
d.mf2(); // fine, calls Base::mf2
d.mf3(); // fine, calls Derived::mf3
d.mf3(x); // error! Derived::mf3 hides Base::mf3
就像你看到的,即使 base 和 derived classes 中的函数具有不同的参数类型,它也同样适用,而且不管函数是 virtual 还是 non-virtual,它也同样适用。覆盖只与名字相关!
解决覆盖的办法:
1.使用using声明式
class Derived: public Base {
public:
using Base::mf1; // make all things in Base named mf1 and mf3
using Base::mf3; // visible (and public) in Derived's scope
virtual void mf1();
void mf3();
void mf4();
...
};
现在 inheritance 就可以起到预期的作用:
Derived d;
int x;
...
d.mf1(); // still fine, still calls Derived::mf1
d.mf1(x); // now okay, calls Base::mf1
d.mf2(); // still fine, still calls Base::mf2
d.mf3(); // fine, calls Derived::mf3
d.mf3(x); // now okay, calls Base::mf3
这意味着如果你从一个带有重载函数的 base class 继承,而且你只想重定义或替换它们中的一部分,你需要为每一个你不想覆盖的名字使用 using declaration。如果你不这样做,一些你希望继承下来的名字会被覆盖。
3.使用转交函数: (该方法通常适用于private继承,因为对public继承来说,任何一件能对base class做的事情也应该可以对derived class生效!!参见条款32)
class Base {
public:
virtual void mf1() = 0;
virtual void mf1(int);
... // as before
};
class Derived: private Base {
public:
virtual void mf1() // forwarding function; implicitly
{ Base::mf1(); } // inline (see Item 30)
...
};
...
Derived d;
int x;
d.mf1(); // fine, calls Derived::mf1
d.mf1(x); // error! Base::mf1() is hidden
总结:
derived classes 中的名字覆盖 base classes 中的名字,在 public inheritance 中,这从来不是想要的。
为了使隐藏的名字重新可见,使用 using declarations 或者 forwarding functions(转调函数)。
相关文章推荐
- Effective C++笔记: 继承和面向对象设计(三)
- Effective C++笔记: 继承和面向对象设计(四)
- Effective C++笔记:继承与面向对象设计
- Effective C++笔记(8)—继承与面向对象设计
- Effective c++(笔记)之继承关系与面向对象设计
- Effective C++笔记(六):继承与面向对象设计
- Effective C++笔记: 继承和面向对象设计(二)
- effective C++ 学习笔记 实现&&继承与面向对象设计
- effective C++笔记之条款39: 避免“向下转换”继承层次
- effective c++条款32~40“继承与面向对象设计”整理
- effective C++笔记之条款35: 使公有继承体现“是一个”的含义
- effective C++读书笔记六 —— 继承与面向对象设计
- Item 33:避免隐藏继承来的名称(继承与作用域嵌套) Effective C++笔记
- effective C++笔记之条款43: 明智地使用多继承(MI)
- effective C++笔记之条款38: 绝不要重新定义继承而来的缺省参数值
- Effective C++(20) 继承与面向对象设计
- Effective C++笔记_条款33确定你的public继承塑模出is-a关系
- Item 39:明智地使用private继承 Effective C++笔记
- effective C++笔记之条款36、37: 区分接口继承和实现继承、绝不要重新定义继承而来的非虚函数
- [Effective C++笔记]条款39:明智而审慎地使用private继承