"The C++ Programming Language"中所述的私有继承用法

Private inheritance usage as described in "The C++ Programming Language"

本文关键字:用法 继承 The C++ Programming Language      更新时间:2023-10-16

C++编程语言,第4版,§20.5.2"访问基类"(第605页)中,它说(关于私有继承):

私有基础在通过限制来定义类时最有用 与基地的接口,以便可以得到更强的保证 提供。例如,B 是 Z 的实现细节。矢量 的指针模板,将类型检查添加到其矢量基础 §25.3就是一个很好的例子。

目前尚不清楚Bjarne Stroustrup在这里想说什么。如何通过将"接口"限制为基来定义类?他所说的"更强的保证"是什么意思?

让我们举一个非常简单的例子:

// A simple class with a *public* member
class A
{
public:
int a;
};
// Use private inheritance
class B : private A
{
public:
int b;
};
// Use public inheritance
class C : public A
{
public:
int c;
};
// ...
B my_b;
my_b.a = 0;  // Invalid, the member a is private due to the private inhericance
C my_c;
my_c.a = 0;  // Valid, because the inheritance is public

private继承限制对基类成员的访问。即使A::a成员变量是public的,由于继承private,它也在子类B中变得private

让我们继续以向量为例。向量只是Ts 的容器。现在,假设您要构建一个行为类似于向量但添加一些其他运行时检查的类型。我现在手头没有TC++PL的副本,所以让我们组成一个约束:例如,假设你的向量只允许容纳偶数。尝试插入奇数将导致运行时错误。让我们称这个新类为even_vector,没有运行时检查的版本base_vector

even_vector提供了比base_vector更强的运行时保证:它保证它的所有元素都是均匀的。

假设您的base_vector被设计为作为基类很好地工作(std::vector通常不会),您现在可能会想使用公共继承来实现base_vector方面的even_vector。毕竟,功能是相同的,您只需在even_vector情况下在base_vector提供的功能之上进行一些额外的运行时检查。但是,如果您在这里使用公共继承,您将违反 Liskov 替换原则:您不能在任何地方使用even_vectorbase_vector。特别是,当您将奇数插入base_vector时,even_vector会中断。这很糟糕,因为现在所有为base_vector编写的代码都必须考虑到某些base_vector无法处理奇数的事实。

使用私有继承,您不会遇到此问题:在这里,even_vectorbase_vector继承的事实是实现的细节。客户端不能在预期base_vector的地方使用even_vector,因此不会出现上面的问题,但我们仍然可以获得代码重用的好处。

话虽如此,使用私有继承进行代码重用是许多人不鼓励的做法。可以说更好的方法是在此处使用组合,即向even_vector添加一个私有base_vector成员。这种方法的优点是它严重减少了两个类之间的耦合,因为even_vector不再能够访问base_vector的任何非公共部分。

如何将

"接口"限制为基数来定义类?

通过使继承私有。当继承是私有的时,基类的接口仅限于成员函数,并且在外部不可用。访问说明符可以在基础列表中给出:

class A : private B
//        ^^^^^^^

他所说的"更强的保证"是什么意思?

任何不是由基地提供的保证,或者是基地提供的保证的超集。

例如,"行为总是被很好地定义"的保证比"只有在输入不为空的情况下行为被很好地定义"更强大。另一个例子:"函数不抛出"比"除非复制构造函数抛出,否则函数不会抛出">强。

让我们看看接口的可能情况,以帮助构建图片。

class poison {
public:
virtual void used() = 0;
};
class food {
public:
virtual void eat();
protected:
void poisonConsumed(poison& p);
}
class cheese : public food, private poison {
public:
virtual void eat() override {
poisonConsumed(*this);
}
private:
void used() override;
}

这向外界展示了奶酪"不是毒药"——也就是说,班外的任何人都不知道它是一种毒药,它可以被制成"不是毒药",而对使用它的任何东西都没有影响。

然而,奶酪可以传递给任何期待毒药的东西,然后可以自由地调用used();即使它在奶酪中是私人的。