条款32:确定你的public继承塑模出is-a关系


以public继承的类,其父类的所有的性质都应该使用与子类,任何需要父类的地方都应该能用子类来代替,任何子类类型的对象也同时是父类的:

class Person{...};
class Student : public Person{...};
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);
eat(s);
study(s);
study(p); //错误

在上面的例子中student是Person的子类,以public继承,因为每个student都是一个Person,每个Person的属性都是适合Student,在现实生活中,我们要注意public的关系,例如对于企鹅和鸟,正方形和矩形等,看起来可能是public继承比较合适,但是企鹅并不含有鸟的一切特征,例如飞,正方形也不具有矩形的所有特征,例如正方形不会出现长和宽不相等的情况,因此在设计public继承的时候要注意!

请记住:

  • public继承意味着"is-a",使用与base-class的每一件事情也一定适用于derived cleass身上,因为没个drived对象一定是个base对象。
条款33:避免遮掩继承而来的名称

对于继承的父类中的函数,如果在子类中有同名的函数,则父类中的同名函数都将被隐藏,例如:

class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int x);
virtual void mf2();
void mf3();
void mf3(doubel);
}; class Derived : public Base{
public:
virtual void mf1();
void mf3();
void mf4();
}; Derived d;
int x;
d.mf1();
d.mf1(x) //错误,父类中的mf1同名的函数被子类中的函数名隐藏
d.mf2(); //调用父类的mf2函数
d.mf3()
d.mf3(x)//错误, 父类中的mf3同名的函数被子类中的函数名隐藏
d.mf4();

在上面的例子中,base类中的mf3、mf4和mf1中的所有同名字的函数都被子类中的函数给隐藏了,即使是参数类型不同的或者virtual和no-virtual函数都是有同样的效果的!为了能够防止父类中的同名函数被隐藏我们可以在子类中用using来引入父类的成员函数如:

class Derived : public Base{
public:
using Base::mf1;//让Base class内名为mf1和mf3的所有东西在Dervied作用域内都是可见的(并且pubilc)
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
};
Derived d;
int x;
d.mf1(); //没有问题,调用Derived::mf1
d.mf1(x) //没有问题,调用Base::mf1
d.mf2(); //没有问题,调用Base::mf2
d.mf3(); //没有问题,调用Derived::mf1
d.mf3(x); //没有问题,调用Base::mf3

在public继承的体系中,子类不应该要隐藏父类的成员函数,因为这样就与public继承的原理要背驰,public继承就是保证父类的所有函数在子类中都是相同的!在子类中主动的调用父类的成员函数我们可以通过Base::mf1来实现!

有时候你并不想继承base classes的所有函数。例如假设Derived以private形式继承Base,而Derived唯一想继承的mf1是那个无参数版本。using声明式在这里派不上用场,因为using声明式会令继承而来的给定名称之所有同名函数在Dervied class中都可见。

class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int x);
virtual void mf2();
void mf3();
void mf3(doubel);
}; class Derived : private Base{
public:
virtual void mf1()
{ Base::mf1(); }
void mf3();
void mf4();
}; Derived d;
int x;
d.mf1(); //调用的是Derived::mf1
d.mf1(x) //错误,Base::mf1(int x)被隐藏

请记住:

  • Drived classes内的名称会遮盖带Base class内的名称,在public继承体系中这样做事不合适的!
  • 为了让被遮掩的名称那个再见天日,可以使用using声明或者转交函数(即在子类函数中主动调用父类的函数通过Base::mf1)

条款34:区分接口继承和实现继承

表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承。这两种继承的差异,就像函数声明与函数定义的区别。

身为class设计者,有时候你会希望derived classes只继承成员函数的接口(也就是声明);有时候你又希望derived classes同时继承函数的接口和实现,但又希望能够覆写它们所继承的实现;又有时候你希望derived classes同时继承函数的接口和实现,并且不允许覆写任何东西。

在对Base class 进行public继承的时候,根据Base class中成员函数的类型我们能够得到不同的继承方式,如下:

class Shape{
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectId() const;
}; class Rectangle:public Shap{...};

在上面的类Shap中,成员函数draw是pure的,因此class 是抽象基类,也就不能有Shap类型的对象,此时draw提供的只是一个函数的接口,在Rectangle类中必须要对这个接口进行实现,这个是接口的继承。当然在Shap基类中,我们可以为pure virtual函数提供定义。也就是说你可以为Share::draw供应一份实现代码,C++并不会发出抱怨,但调用它的唯一途径是“调用时明确指出其class名称”:

Shape* ps = new Shape;//错误!Shape是抽象类
Shape* ps1 = new Rectangle;//OK
ps1->draw();//调用Rectangle::draw
ps1->Shape::draw();//调用Shape::draw

对于error的函数,是普通的virtual函数,它对其子类提供的是接口和默认实现的继承。声明普通的virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。也就是说Shape::error的声明式告诉derived classes的设计者,“你必须支持一个error函数,但如果你不想自己写一个,可以使用shape class提供的缺省版本”。
       在objectId成员函数中,我们定义的是普通的成员函数,对于public继承的体系来说我们不应该在子类中对该成员函数进行重定义,因为这会覆盖掉父类函数的定义!

请记住:

  • 接口继承和实现继承不同,在public继承下,derived classes总是继承base class的接口。
  • pure virtual函数只是具体的指定接口继承。
  • 普通的virutal 函数具体制定接口继承以及缺省的实现继承。
  • non-virtual函数具体制定接口继承以及强制性实现继承。

条款35:考虑virtual函数以外的其他选择

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

class B{
public:
void func();
...
};
class D:public B{
public:
...
};

在上面的简单例子中,如果有下面的调用:

D x;
B* p = new D();
D* q = new D();
p->func();
q->func();

此时上面的两个调用调用的都是B类的func函数,此时如果在D类中也重新定义函数void func(),此时如果还是上面那样的调用第一个将调用的是B类的func,第二个调用的是D类的func,这是因为对普通的函数我们是采用的静态链接,因此对于p虽然指向的是D类的一个对象,但是因为在静态连接阶段不能够知道其具体指向的对象,因此指向的还是B类的func,在前面的条款中我们说过对于public继承的体系中,父类的成员函数是完全适用于子类的,但是如果重载了函数的实现,那么这个条款将不适合,因此在public继承体系中,子类可以重新定义父类的virtual的接口函数,而非实现函数!

请记住:

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

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

你只能继承两种函数:virtual和non-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 ;
....
}; Shape* ps; //静态类型是Shape*
Shape* pc = new Circle;//静态类型是Shape*
Shape* pr = new Rectangle;//静态类型是Shape*
ps = pc;//动态类型是Circle*
ps = pr;//动态类型是Rectangle*
pc->draw(Shape::Red);//调用Circle::draw(Shape::Red)
pr->draw(Shape::Red);//调用Rectangle::draw(Shape::Red)
pr->draw();////调用Rectangle::draw(Shape::Red)

请记住

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

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

条款39:明智而审慎的使用private继承

先说private继承的特点:

1.也就是说,编译器不会讲一个private继承而来的派生类对象转化为一个基类对象。这意味着,priavte继承不再是is-a关系:

class Person
{
protected:
string name;
}; class Student:private Person
{
private:
string schoolNumber;
}; void eat(const Person& p)
{
cout<<"eat"<<endl;
} void study(const Student& s)
{
cout<<"study"<<endl;
} int main()
{
Person p1;
eat(p1);
Student s1;
study(s1);
// eat(s1);错误
return 0;
}

2.基类的public和protected成员在派生类中全为private属性。
       因此,private继承意味着:根据某物实现。这与前面条款介绍的复合很类似。在大多数时候,我们应该使用复合,而不是private继承来实现这种功能。但是当有protected成员和虚函数牵扯进来的时候,我们又不得不用private继承。

其次,与复合相比,private可以使空基类的最优化。先看一个例子:

//定义一个空基类
class Empty{}; class HoldsAnInt
{
private:
int x;
Empty e;//复合
};

sizeof(int)为4,sizeof(Empty)为1,sizeof(HoldsAnInt)为8。首先,类的大小取决于其数据成员的大小。sizeof(Empty)应该为0,但是由于在编译器会将它的大小设为1,而“齐位需求”会将它放大为1个int,所以,sizeof(HoldsAnInt)为8。但是,如果使用的是private继承来实现,就不存在这种问题了:sizeof(HoldsAnInt)只有一个int的大小:4。

如果classes之间的继承关系是Private,编译器不会自动将一个derived class对象转换为一个base class对对象。第二是,由private base class继承而来的所有成员,在derived class中都会变成private属性。

private继承纯粹只是一种实现技术。如果D以private继承了B,你的用意是为了采用B中已经备妥的某些特性。

总之,private继承意味着根据某物实现。当派生类需要访问基类的的受保护成员或者重新定义虚函数时,我们才使用它。而且private继承可以是得空基类最优化,如果在开发中需要是得对象尺寸最小,那么也用得着它。

条款40:明智而审慎的使用多继承

多继承的歧义:

class Borrow
{
public:
void check();
....
};
class Elect
{
private:
bool check();
....
};
class MP3:public Borrow,public Elect
{
.....
};
MP3 pm3;
mp3.check();// 歧义

为了解决这个歧义,你必须明白指出你要调用哪一个base class内的函数:

mp3.Borrow::check();

这还不是最可怕的情况,最可怕的是出现“钻石型多重继承”:B和C继承自A,而且D继承自B和C。此时,理论上讲,D中会有两份A的public成员(这里假定是public继承),但实际上,大多数时候,我们只希望有一份,此时只能通过虚继承来避免这种现象。
              

Effective C++ ——继承与面向对象设计的更多相关文章

  1. Effective C++ —— 继承与面向对象设计(六)

    条款32 : 确定你的public继承塑模出is-a关系 以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味“is-a”(是一种)的关系.请务必牢记.当 ...

  2. Effective C++ -- 继承和面向对象设计

    32.确保你的public继承了模is-a关系 public继承意味着is-a关系(里氏替换原则),一切适用于基类也适用于派生类. 矩形继承正方形问题: 可实施与矩形的操作无法实施与正方形 在编程领域 ...

  3. EffectiveC++ 第6章 继承与面向对象设计

    我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 6 继承与面向对象设计 Inheritance and ...

  4. Effective C++(20) 继承与面向对象设计

    本文主要参考<Effective C++ 3rd>中的第六章部分章节的内容. 关注的问题集中在继承.派生.virtual函数等.如: virtual? non-virtual? pure ...

  5. Effective C++: 06继承与面向对象设计

    32:确定你的public继承塑模出is-a关系 以C++进行面向对象编程,最重要的一个规则是:public继承表示的是"is-a"(是一种)的关系. 如果令class D以pub ...

  6. Effective C++ 6.继承与面向对象设计

    //条款32:确定你的public继承塑模出is-a关系 // 1.public继承意味着is-a的关系,适用在基类上的方法都能用于派生类上. //条款33:避免遮掩继承而来的名称 // 1.在pub ...

  7. Effective C++笔记:继承与面向对象设计

    关于OOP 博客地址:http://www.cnblogs.com/ronny 转载请注明出处! 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private ...

  8. Effective C++笔记(六):继承与面向对象设计

    参考:http://www.cnblogs.com/ronny/p/3756494.html 条款32:确定你的public继承塑模出is-a关系 “public继承”意味着is-a.适用于base ...

  9. 【Effective C++】继承与面向对象设计

    关于OOP 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtu ...

随机推荐

  1. [JSOI2015]非诚勿扰

    Description [故事背景] JYY赶上了互联网创业的大潮,为非常勿扰开发了最新的手机App实现单身 大龄青年之间的“速配”.然而随着用户数量的增长,JYY发现现有速配的算法似 乎很难满足大家 ...

  2. ●UOJ58 [WC2013]糖果公园

    题链: http://uoj.ac/problem/58题解: 树上带修莫队. 每个块的大小为$n^{\frac{2}{3}}$,在dfs时,把点集分为若干块. 然后类似序列带修莫队,三个关键字:be ...

  3. UVA11404:Palindromic Subsequence

    回文子串dp,最小字典序的话需要记录一下,注意是string型的,不能只记录一个字符,因为可能出现相等的情况 #include<cstdio> #include<cstdlib> ...

  4. ●BZOJ 1934 [Shoi2007]Vote 善意的投票

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=1934 题解: 题目有点迷. S向为1的点连边,为0的点向T连边,在有关系的两个点之间连双向边 ...

  5. [Codeforces]906D Power Tower

    虽说是一道裸题,但还是让小C学到了一点姿势的. Description 给定一个长度为n的数组w,模数m和询问次数q,每次询问给定l,r,求: 对m取模的值. Input 第一行两个整数n,m,表示数 ...

  6. java开发小技巧

    链接地址:http://www.cnblogs.com/zkh101/p/8083368.html 人脸识别地址:http://blog.csdn.net/gitchat/article/detail ...

  7. Spring MVC运行流程

    一.配置阶段 ①web.xml ②DispatcherServlet    //Spring MVC总入口 ③配置初始化参数   //classpath:application.xml,用于配置无数个 ...

  8. 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...

  9. Redis开启远程登录连接

    Redis 安装详见 http://www.cnblogs.com/zendwang/p/6560628.html 当前安装测试版本:Redis 3.2.8 默认安装完毕Redis只能本机访问 [ro ...

  10. Docker安装tomcat和部署项目

    随着微服务的流行,Docker越来越流行,正如它的理念"Build, Ship, and Run Any App, Anywhere"一样,Docker提供的容器隔离技术使得开发人 ...