Inheritance___CH_17

2023-11-04

17.1 — Introduction to inheritance

A hierarchy is a diagram that shows how various objects are related. Most hierarchies either show a progression over time (386 -> 486 -> Pentium), or categorize things in a way that moves from general to specific (fruit -> apple -> red delicious). If you’ve ever taken biology, the famous domain, kingdom, phylum, class, order, family, genus, and species ordering defines a hierarchy (from general to specific).

Here’s another example of a hierarchy: a square is a rectangle, which is a quadrilateral, which is a shape. A right triangle is a triangle, which is also a shape. Put into a hierarchy diagram, that would look like this:在这里插入图片描述
This diagram goes from general (top) to specific (bottom), with each item in the hierarchy inheriting the properties and behaviors of the item above it.

17.2 — Basic inheritance in C++

A BaseballPlayer class

Have BaseballPlayer inherit those attributes from Person. Remember that inheritance represents an is-a relationship. Is a BaseballPlayer a Person? Yes, it is. So inheritance is a good choice here.

Making BaseballPlayer a derived class

To have BaseballPlayer inherit from our Person class, the syntax is fairly simple. After the class BaseballPlayer declaration, we use a colon, the word “public”, and the name of the class we wish to inherit. This is called public inheritance. We’ll talk more about what public inheritance means in a future lesson.

#include <iostream>
#include <string>

class Person
{
public:
    std::string m_name{};
    int m_age{};

    Person(const std::string& name = "", int age = 0)
        : m_name{name}, m_age{age}
    {
    }

    const std::string& getName() const { return m_name; }
    int getAge() const { return m_age; }

};

// BaseballPlayer publicly inheriting Person
class BaseballPlayer : public Person
{
public:
    double m_battingAverage{};
    int m_homeRuns{};

    BaseballPlayer(double battingAverage = 0.0, int homeRuns = 0)
       : m_battingAverage{battingAverage}, m_homeRuns{homeRuns}
    {
    }
};

int main()
{
    // Create a new BaseballPlayer object
    BaseballPlayer joe{};
    // Assign it a name (we can do this directly because m_name is public)
    joe.m_name = "Joe";
    // Print out the name
    std::cout << joe.getName() << '\n'; // use the getName() function we've acquired from the Person base class

    return 0;
}

Which prints the value:

Joe

Inheritance chains

Why is this kind of inheritance useful?

Inheriting from a base class means we don’t have to redefine the information from the base class in our derived classes. We automatically receive the member functions and member variables of the base class through inheritance, and then simply add the additional functions or member variables we want. This not only saves work, but also means that if we ever update or modify the base class (e.g. add new functions, or fix a bug), all of our derived classes will automatically inherit the changes!

For example, if we ever added a new function to Person, both Employee and Supervisor would automatically gain access to it. If we added a new variable to Employee, Supervisor would also gain access to it. This allows us to construct new classes in an easy, intuitive, and low-maintenance way!

Conclusion

Inheritance allows us to reuse classes by having other classes inherit their members.

17.3 — Order of construction of derived classes

When C++ constructs derived objects, it does so in phases. First, the most-base class (at the top of the inheritance tree) is constructed first. Then each child class is constructed in order, until the most-child class (at the bottom of the inheritance tree) is constructed last.

#include <iostream>

class Base
{
public:
    int m_id {};

    Base(int id=0)
        : m_id { id }
    {
        std::cout << "Base\n";
    }

    int getId() const { return m_id; }
};

class Derived: public Base
{
public:
    double m_cost {};

    Derived(double cost=0.0)
        : m_cost { cost }
    {
        std::cout << "Derived\n";
    }

    double getCost() const { return m_cost; }
};

int main()
{
    std::cout << "Instantiating Base\n";
    Base base;

    std::cout << "Instantiating Derived\n";
    Derived derived;

    return 0;
}

This program produces the following result:

Instantiating Base
Base
Instantiating Derived
Base
Derived

As you can see, when we constructed Derived, the Base portion of Derived got constructed first. This makes sense: logically, a child can not exist without a parent. It’s also the safe way to do things: the child class often uses variables and functions from the parent, but the parent class knows nothing about the child. Instantiating the parent class first ensures those variables are already initialized by the time the derived class is created and ready to use them.

Order of construction for inheritance chains

It is sometimes the case that classes are derived from other classes, which are themselves derived from other classes. For example:

#include <iostream>

class A
{
public:
    A()
    {
        std::cout << "A\n";
    }
};

class B: public A
{
public:
    B()
    {
        std::cout << "B\n";
    }
};

class C: public B
{
public:
    C()
    {
        std::cout << "C\n";
    }
};

class D: public C
{
public:
    D()
    {
        std::cout << "D\n";
    }
};

Remember that C++ always constructs the “first” or “most base” class first. It then walks through the inheritance tree in order and constructs each successive derived class.

Here’s a short program that illustrates the order of creation all along the inheritance chain.

int main()
{
    std::cout << "Constructing A: \n";
    A a;

    std::cout << "Constructing B: \n";
    B b;

    std::cout << "Constructing C: \n";
    C c;

    std::cout << "Constructing D: \n";
    D d;
}

This code prints the following:

Constructing A:
A
Constructing B:
A
B
Constructing C:
A
B
C
Constructing D:
A
B
C
D

Conclusion

C++ constructs derived classes in phases, starting with the most-base class (at the top of the inheritance tree) and finishing with the most-child class (at the bottom of the inheritance tree). As each class is constructed, the appropriate constructor from that class is called to initialize that part of the class.

You will note that our example classes in this section have all used base class default constructors (for simplicity). In the next lesson, we will take a closer look at the role of constructors in the process of constructing derived classes (including how to explicitly choose which base class constructor you want your derived class to use).

17.4 — Constructors and initialization of derived classes

Initializing base class members

Fortunately, C++ gives us the ability to explicitly choose which Base class constructor will be called! To do this, simply add a call to the Base class constructor in the member initializer list of the derived class:

class Derived: public Base
{
public:
    double m_cost {};

    Derived(double cost=0.0, int id=0)
        : Base{ id } // Call Base(int) constructor with value id!
        , m_cost{ cost }
    {
    }

    double getCost() const { return m_cost; }
};

Now, when we execute this code:

#include <iostream>

int main()
{
    Derived derived{ 1.3, 5 }; // use Derived(double, int) constructor
    std::cout << "Id: " << derived.getId() << '\n';
    std::cout << "Cost: " << derived.getCost() << '\n';

    return 0;
}

The base class constructor Base(int) will be used to initialize m_id to 5, and the derived class constructor will be used to initialize m_cost to 1.3!

Thus, the program will print:

Id: 5
Cost: 1.3

Note that it doesn’t matter where in the Derived constructor member initializer list the Base constructor is called – it will always execute first.

Now we can make our members private

#include <iostream>

class Base
{
private: // our member is now private
    int m_id {};

public:
    Base(int id=0)
        : m_id{ id }
    {
    }

    int getId() const { return m_id; }
};

class Derived: public Base
{
private: // our member is now private
    double m_cost;

public:
    Derived(double cost=0.0, int id=0)
        : Base{ id } // Call Base(int) constructor with value id!
        , m_cost{ cost }
    {
    }

    double getCost() const { return m_cost; }
};

int main()
{
    Derived derived{ 1.3, 5 }; // use Derived(double, int) constructor
    std::cout << "Id: " << derived.getId() << '\n';
    std::cout << "Cost: " << derived.getCost() << '\n';

    return 0;
}

In the above code, we made m_id and m_cost private. This is fine, since we use the relevant constructors to initialize them, and use a public accessor to get the values.

This prints, as expected:

Id: 5
Cost: 1.3

We’ll talk more about access specifiers in the next lesson.

Another example

Inheritance chains

#include <iostream>

class A
{
public:
    A(int a)
    {
        std::cout << "A: " << a << '\n';
    }
};

class B: public A
{
public:
    B(int a, double b)
    : A{ a }
    {
        std::cout << "B: " << b << '\n';
    }
};

class C: public B
{
public:
    C(int a, double b, char c)
    : B{ a, b }
    {
        std::cout << "C: " << c << '\n';
    }
};

int main()
{
    C c{ 5, 4.3, 'R' };

    return 0;
}

In this example, class C is derived from class B, which is derived from class A. So what happens when we instantiate an object of class C?

First, main() calls C(int, double, char). The C constructor calls B(int, double). The B constructor calls A(int). Because A does not inherit from anybody, this is the first class we’ll construct. A is constructed, prints the value 5, and returns control to B. B is constructed, prints the value 4.3, and returns control to C. C is constructed, prints the value ‘R’, and returns control to main(). And we’re done!Thus, this program prints:

A: 5
B: 4.3
C: R

It is worth mentioning that constructors can only call constructors from their immediate parent/base class. Consequently, the C constructor could not call or pass parameters to the A constructor directly. The C constructor can only call the B constructor (which has the responsibility of calling the A constructor).

Destructors

When a derived class is destroyed, each destructor is called in the reverse order of construction. In the above example, when c is destroyed, the C destructor is called first, then the B destructor, then the A destructor.

Summary

When constructing a derived class, the derived class constructor is responsible for determining which base class constructor is called. If no base class constructor is specified, the default base class constructor will be used. In that case, if no default base class constructor can be found (or created by default), the compiler will display an error. The classes are then constructed in order from most base to most derived.

At this point, you now understand enough about C++ inheritance to create your own inherited classes!

17.5 — Inheritance and access specifiers

The protected access specifier

C++ has a third access specifier that we have yet to talk about because it’s only useful in an inheritance context. The protected access specifier allows the class the member belongs to, friends, and derived classes to access the member.

class Base
{
public:
    int m_public {}; // can be accessed by anybody
protected:
    int m_protected {}; // can be accessed by Base members, friends, and derived classes
private:
    int m_private {}; // can only be accessed by Base members and friends (but not derived classes)
};

class Derived: public Base
{
public:
    Derived()
    {
        m_public = 1; // allowed: can access public base members from derived class
        m_protected = 2; // allowed: can access protected base members from derived class
        m_private = 3; // not allowed: can not access private base members from derived class
    }
};

int main()
{
    Base base;
    base.m_public = 1; // allowed: can access public members from outside class
    base.m_protected = 2; // not allowed: can not access protected members from outside class
    base.m_private = 3; // not allowed: can not access private members from outside class

    return 0;
}

In the above example, you can see that the protected base member m_protected is directly accessible by the derived class, but not by the public.

So when should I use the protected access specifier?

Best practice

Favor private members over protected members.

Different kinds of inheritance, and their impact on access

// Inherit from Base publicly
class Pub: public Base
{
};

// Inherit from Base protectedly
class Pro: protected Base
{
};

// Inherit from Base privately
class Pri: private Base
{
};

class Def: Base // Defaults to private inheritance
{
};

If you do not choose an inheritance type, C++ defaults to private inheritance (just like members default to private access if you do not specify otherwise).

Public inheritance

class Base
{
public:
    int m_public {};
protected:
    int m_protected {};
private:
    int m_private {};
};

class Pub: public Base // note: public inheritance
{
    // Public inheritance means:
    // Public inherited members stay public (so m_public is treated as public)
    // Protected inherited members stay protected (so m_protected is treated as protected)
    // Private inherited members stay inaccessible (so m_private is inaccessible)
public:
    Pub()
    {
        m_public = 1; // okay: m_public was inherited as public
        m_protected = 2; // okay: m_protected was inherited as protected
        m_private = 3; // not okay: m_private is inaccessible from derived class
    }
};

int main()
{
    // Outside access uses the access specifiers of the class being accessed.
    Base base;
    base.m_public = 1; // okay: m_public is public in Base
    base.m_protected = 2; // not okay: m_protected is protected in Base
    base.m_private = 3; // not okay: m_private is private in Base

    Pub pub;
    pub.m_public = 1; // okay: m_public is public in Pub
    pub.m_protected = 2; // not okay: m_protected is protected in Pub
    pub.m_private = 3; // not okay: m_private is inaccessible in Pub

    return 0;
}

Best practice

Use public inheritance unless you have a specific reason to do otherwise.

Protected inheritance

Protected inheritance is the least common method of inheritance. It is almost never used, except in very particular cases. With protected inheritance, the public and protected members become protected, and private members stay inaccessible.

Private inheritance

With private inheritance, all members from the base class are inherited as private. This means private members are inaccessible, and protected and public members become private.

Note that this does not affect the way that the derived class accesses members inherited from its parent! It only affects the code trying to access those members through the derived class.

class Base
{
public:
    int m_public {};
protected:
    int m_protected {};
private:
    int m_private {};
};

class Pri: private Base // note: private inheritance
{
    // Private inheritance means:
    // Public inherited members become private (so m_public is treated as private)
    // Protected inherited members become private (so m_protected is treated as private)
    // Private inherited members stay inaccessible (so m_private is inaccessible)
public:
    Pri()
    {
        m_public = 1; // okay: m_public is now private in Pri
        m_protected = 2; // okay: m_protected is now private in Pri
        m_private = 3; // not okay: derived classes can't access private members in the base class
    }
};

int main()
{
    // Outside access uses the access specifiers of the class being accessed.
    // In this case, the access specifiers of base.
    Base base;
    base.m_public = 1; // okay: m_public is public in Base
    base.m_protected = 2; // not okay: m_protected is protected in Base
    base.m_private = 3; // not okay: m_private is private in Base

    Pri pri;
    pri.m_public = 1; // not okay: m_public is now private in Pri
    pri.m_protected = 2; // not okay: m_protected is now private in Pri
    pri.m_private = 3; // not okay: m_private is inaccessible in Pri

    return 0;
}

In practice, private inheritance is rarely used.

A final example

Summary

The way that the access specifiers, inheritance types, and derived classes interact causes a lot of confusion. To try and clarify things as much as possible:

First, a class (and friends) can always access its own non-inherited members. The access specifiers only affect whether outsiders and derived classes can access those members.

Second, when derived classes inherit members, those members may change access specifiers in the derived class. This does not affect the derived classes’ own (non-inherited) members (which have their own access specifiers). It only affects whether outsiders and classes derived from the derived class can access those inherited members.

As a final note, although in the examples above, we’ve only shown examples using member variables, these access rules hold true for all members (e.g. member functions and types declared inside the class).

17.6 — Adding new functionality to a derived class

Adding new functionality to a derived class

class Derived: public Base
{
public:
    Derived(int value)
        : Base { value }
    {
    }

    int getValue() const { return m_value; }
};

17.7 — Calling inherited functions and overriding behavior

By default, derived classes inherit all of the behaviors defined in a base class. In this lesson, we’ll examine in more detail how member functions are selected, as well as how we can leverage this to change behaviors in a derived class.

Calling a base class function

Redefining behaviors

Note that when you redefine a function in the derived class, the derived function does not inherit the access specifier of the function with the same name in the base class. It uses whatever access specifier it is defined under in the derived class. Therefore, a function that is defined as private in the base class can be redefined as public in the derived class, or vice-versa!

#include <iostream>

class Base
{
private:
	void print() const
	{
		std::cout << "Base";
	}
};

class Derived : public Base
{
public:
	void print() const
	{
		std::cout << "Derived ";
	}
};


int main()
{
	Derived derived;
	derived.print(); // calls derived::print(), which is public
	return 0;
}

Adding to existing functionality

To have a derived function call a base function of the same name, simply do a normal function call, but prefix the function with the scope qualifier (the name of the base class and two colons). The following example redefines Derived::identify() so it first calls Base::identify() and then does its own additional stuff.

#include <iostream>

class Derived: public Base
{
public:
    Derived(int value)
        : Base { value }
    {
    }

    int getValue() const  { return m_value; }

    void identify() const
    {
        Base::identify(); // call Base::identify() first
        std::cout << "I am a Derived\n"; // then identify ourselves
    }
};

Now consider the following example:

int main()
{
    Base base { 5 };
    base.identify();

    Derived derived { 7 };
    derived.identify();

    return 0;
}
I am a Base
I am a Base
I am a Derived

When derived.identify() is executed, it resolves to Derived::identify(). However, the first thing Derived::identify() does is call Base::identify(), which prints “I am a Base”. When Base::identify() returns, Derived::identify() continues executing and prints “I am a Derived”.

This should be pretty straightforward. Why do we need to use the scope resolution operator (:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Inheritance___CH_17 的相关文章

随机推荐

  • 【Linux】用户组与文件目录权限

    Linux用户与组 Linux本身是个多用户多任务的操作系统 用户账户分类 root用户 UID 0 超级用户 能跨越一切用户和组群对所有文件或目录进行读取 修改 删除 系统用户 UID 1 999 虚拟用户 不具有登入Linux的能力 是
  • java中Graphics类的使用

    绘图 很多程序如各种小游戏都需要在窗口中绘制各种图形 除此之外 即使在开发JavaEE项目时 有 时候也必须 动态 地向客户 端生成各种图形 图表 比如 图形验证码 统计图等 这都需要利用AWT的绘图功能 组件绘图原理 之前我们已经学习过很
  • xtu oj 1328 数码和

    题目描述 一个10进制数n在2 16进制下可以得到的不同的数码和 求在这些数码和中出现次数最多的数码和 比如20 其中数码和2和4分别出现了3次 为最多出现次数 输入 第一行是一个整数T 1 T 1000 表示样例的个数 以后每行一个整数n
  • 企业研发提效抓手,揭秘云原生的效能“奇点”

    导语 在云原生时代 研发效能治理面临新的挑战 同时也获得了新的视角 如何更好地利用云原生技术的优势 从而在根本上提升研发效能 已成为许多企业数字化转型过程中的 必答题 今天 我们特别邀请了 Thoughtworks 创新技术总经理 腾讯云
  • linux kernel文件系统数据结构file_system_type

    linux kernel文件系统数据结构file system type 文件系统类型用于表示各种不同的文件系统 如fat sysfs proc等等 对于每个不同的文件系统 都以struct file system type进行描述 内核将
  • 202310读书笔记|《大白鲸原创图画书优秀作品:虾一跳》——蝴蝶效应之最,你值得一读

    202310读书笔记 大白鲸原创图画书优秀作品 虾一跳 蝴蝶效应之最 你值得一读 大白鲸原创图画书优秀作品 虾一跳 作者 耿彦红 文 齐海潮 图 由虾一跳的连锁反应构成了整本书的故事脉络 很生动 故事及叙述的重复都不冗杂 反而很朗朗上口 并
  • 密码学--CTF Crypto 总结

    密码学简介 密码学 Cryptography 一般可分为古典密码学和现代密码学 其中 古典密码学 作为一种实用性艺术存在 其编码和破译通常依赖于设计者和敌手的创造力与技巧 并没有对密码学原件进行清晰的定义 其主要包含以下几个方面 单表替换加
  • TCP与UDP

    前言 TCP和UDP是两个传输层最有代表性的传输层协议 TCP一般提供可靠的信息传输 而UDP常被用于广播和细节控制交给应用的通信传输 传输层的定义 在传输层 IP首部有一个协议字段 用来区分使用的是什么协议 用端口号进行处理的具体程序 在
  • 在Vitis IDE中使用第三方库 libtiff 保存 tiff 文件

    目的和思路 一个Vitis IDE 裸机项目 需要将视频帧无损地保存下来 由于每帧的像素数据是 16bit 1通道的 bayer 格式 满足这一需求的图像格式似乎只有 tiff 格式 开源的tiff 库是 libtiff 而在 Vitis
  • nginx安装及部署

    下载 官方网站 https nginx org en download html Windows下安装 安装 下载后解压 切记不能含有中文路径 文件结构如图 我解压的路径就有中文 记得拷贝放置于英文目录下即可 启动 两种方法 1 直接双击该
  • C语言(函数与预处理、指针)

    一 函数与预处理 一 一维数组 1 一维数组的定义格式为 类型说明符 数组名 常量表达式 例如 int a 10 它表示定义了一个整形数组 数组名为a 有10个元素 2 在定义数组时 需要指定数组中元素的个数 方括弧中的常量表达式用来表示元
  • 基于 FFmpeg 的跨平台视频播放器简明教程(七):使用多线程解码视频和音频

    系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程 一 FFMPEG Conan 环境集成 基于 FFmpeg 的跨平台视频播放器简明教程 二 基础知识和解封装 demux 基于 FFmpeg 的跨平台视频播放器简明教程 三 视频
  • 69. Sqrt(x)

    Implement int sqrt int x Compute and return the square root of x where x is guaranteed to be a non negative integer Sinc
  • 新闻分析:解密代号A1S

    本周二SAP董事长特拉普纳 Hasso Plattner 在Software 2007会议上发言时阐述了SAP新的软件设计方法 SAP表示在过去的三年中有3000多名工程师都在运用这种新的软件设计方法在开发代号为A1S的新产品 虽然这一代号
  • HTML的input类型为hidden导致无法reset改字段的value问题

    问题关键 根据HTML规范 hidden是非ui类元素 不接受用户处理 所以form的 reset并不影响它 http stackoverflow com questions 6367793 why does the reset butto
  • 一种通用的业务监控触发方案设计

    一 背景 业务监控是指通过技术手段监控业务代码执行的最终结果或者状态是否符合预期 实现业务监控主要分成两步 一 在业务系统中选择节点发送消息触发业务监控 二 系统在接收到mq消息或者定时任务调度时 根据消息中或者任务中的业务数据查询业务执行
  • go-micro 在linux下安装出现service auth not found

    1 安装micro linux下执行该命令 wget q https raw githubusercontent com micro micro master scripts install sh O bin bash 2 micro se
  • vue 实现md5、base64加密

    背景 前端使用密码登录的时候 一般都会使用密文传输 否则控制台就能看到密码 具体实现如下 使用 md5 进行加密 1 安装 cnpm install save js md5 2 在 main js 全局引用 import md5 from
  • pycharm console 报错

    描述 pycharm的console本来用的好好地 但是我也不知道自己改了啥 结果报错了 报错 Error Console process terminated with error Traceback most recent call l
  • Inheritance___CH_17

    17 1 Introduction to inheritance A hierarchy is a diagram that shows how various objects are related Most hierarchies ei