C++之代码复用(十)

本文记录了C++中与 代码复用 相关的容易遗忘的一些知识。


C++的主要目标之一是促进代码的重用。代码复用的机制有以下几种:

  • 公有继承是实现这一目标的一种机制,但并非唯一机制。

  • 一种技术是使用本身就是 另一个类的对象 的类成员,这被称为包含、组合或分层。

  • 另一种选择是使用私有继承或保护继承。

  • 包含、私有继承和保护继承通常用于实现“有一个”(has-a)关系。

  • 类(函数)模板,提供了另一种代码重用的方式。


包含对象成员的类

接口与实现

通过公有继承,一个类会继承接口,或许还会继承实现。(基类中的纯虚函数可以提供接口而不提供实现。)获得接口是“是一个”关系的一部分。另一方面,通过组合,一个类会获得实现但不会获得接口。不继承接口是“有一个”关系的一部分。

成员初始化列表顺序

当你有一个成员初始化列表来初始化多个项时,这些项的初始化顺序是它们在类中被声明的顺序,而不是它们在初始化列表中或构造函数参数的书写顺序。

例如,假设编写一个Student构造函数:

Student(const char * str, const double * pd, int n)
    : scores(pd, n), name(str) {}

由于name成员在类定义中是首先声明的,所以它仍会首先被初始化。对于这个例子来说,确切的初始化顺序并不重要,但如果代码将一个成员的值用作另一个成员的初始化表达式的一部分,那么初始化顺序就会很重要。


私有/保护继承

私有继承

通过私有继承,基类的公有成员和保护成员会成为派生类的私有成员。这意味着基类的方法不会成为派生类对象的公有接口的一部分。不过,它们可以在派生类的成员函数内部使用。

在公有继承中,基类的公有方法会成为派生类的公有方法。简而言之,派生类继承了基类的接口。这是“是一个”关系的一部分。而在私有继承中,基类的公有方法会成为派生类的私有方法。简而言之,派生类不会继承基类的接口。正如在包含对象的情况中所看到的,这种不继承接口是“有一个”关系的一部分。

包含是将一个对象作为命名的成员对象添加到类中,而私有继承是将一个对象作为未命名的继承对象添加到类中。

因此,私有继承提供了与包含相同的特性:获得实现,但不获得接口。所以,它也可以用来实现“有一个”关系。

保护继承

受保护继承是私有继承的一种变体。受保护继承和私有继承之间的主要区别在于,当从派生类再派生出另一个类时。在私有继承的情况下,这个第三代类无法在内部使用基类的接口。这是因为基类的公有方法会变成在派生类中是私有的,并且私有成员和方法不能被下一级派生类直接访问。在保护继承的情况下,基类的公共方法在第二代中会变成受保护的,因此可被下一级派生类内部使用。

一般来说,应该使用包含来建模“有一个”(has-a)关系。如果新类需要访问原始类中的受保护成员,或者需要重定义虚函数,那么应该使用私有继承。

继承的种类

下表总结了公有继承、私有继承和保护继承。术语 implicit upcasting 指的是无需使用显式类型转换,就可以让基类指针或引用指向派生类对象。

Property Public Inheritance Protected Inheritance Private Inheritance
Public members become Public members of the derived class Protected members of the derived class Private members of the derived class
Protected members become Protected members of the derived class Protected members of the derived class Private members of the derived class
Private members become Accessible only through the base-class interface Accessible only through the base-class interface Accessible only through the base-class interface
Implicit upcasting Yes Yes (but only within the derived class) No

using重定义访问方式

当使用保护派生或私有派生时,基类的公有成员会变成保护成员或私有成员。假设希望让基类的某个特定方法在派生类中可以公开使用,一种选择是定义一个派生类方法来调用该基类方法。例如,假设你希望Student类能够使用valarraysum()方法,可以在类声明中声明一个sum()方法,然后像这样定义该方法:

double Student::sum() const // 公有Student方法
{
    return std::valarray<double>::sum(); // 使用私有继承的方法
}

一种替代方案可以不用将一个函数调用嵌套在另一个函数调用中,那就是使用using声明(就像在命名空间中使用的那些声明一样)来表明,即使派生是私有继承的,派生类也可以使用某个特定的基类成员。例如,假设你希望能够将valarraymin()max()方法在Student中使用,在这种情况下可以,在公共部分添加using声明:

class Student : private std::string, private std::valarray<double>
{
    public:
    using std::valarray<double>::min;
    using std::valarray<double>::max;
    ...
};

using声明使得valarray<double>::min()valarray<double>::max()方法可用,就好像它们是公共的Student方法一样。

using声明方法仅适用于继承,不适用于包含。


多重继承

多重继承的两个主要问题是:从两个不同的基类继承具有相同名称的不同方法,以及通过两个或多个相关的直接基类继承一个类的多个实例。


类模板

模板特化

隐式实例化

显式实例化

显式特化

部分特化

模板类与友元

模板类声明也可以有友元。可以将模板的友元分为三类:

  • 非模板友元
  • 绑定模板友元,指的是当类被实例化时,友元的类型由该类的类型决定
  • 非绑定模板友元,指的是友元的所有特化版本都是该类每个特化版本的友元

非模板友元

绑定模板友元

非绑定模板友元

模板别名

posted @ 2026-01-12 16:55  Invinc-Z  阅读(2)  评论(0)    收藏  举报