为什么c++允许对公共继承基方法的访问限制

Why does C++ allow access restrictions on public inherited base methods?

本文关键字:方法 访问 继承 c++ 为什么      更新时间:2023-10-16

关于"如何从基类公开继承,但在派生类中使基类的一些公共方法私有?"的问题,我有一个后续问题:

我可以理解c++标准允许派生类放宽继承方法的访问限制,但我想不出任何合法的用例,在派生类中施加访问限制是有意义的。

根据我对继承概念的理解,如果类Derived是公共类Base,那么你可以用Base做的任何事情也可以用Derived做。如果不希望派生类实现Base类的接口,那么首先就不应该使用(公共)继承。(事实上,当我在ROOT的TH2::Fill(double)中遇到这种技术时,这是一个明显的继承滥用案例。)

对于虚拟方法,Derived中的访问限制也是无用的,因为派生的任何用户都可以通过将派生*转换为Base*来使用它们。

因此,从我有限的c++新手的角度来看,这些限制是误导的(派生的程序员可能会假设他的虚拟现在受保护的方法不会被任何人调用,而实际上它可能会),并且还使我对公共继承应该意味着什么感到困惑。

是否有一些合法的用例我错过了?

选自Herb Sutter,本周导师#18:

准则#3:只有当派生类需要调用虚函数的基类实现时,才使虚函数受保护。

关于详细的答案,请阅读我在下面代码中的评论:

#include <iostream>
#include <typeinfo>
#include <memory>
struct Ultimate_base {
    virtual ~Ultimate_base() {}
    void foo() { do_foo(); }
protected:
    // Make this method protected, so the derived class of this class
    // can invoke this implementation
    virtual void do_foo() { std::cout << "Ultimate_base::foo"; }
};
struct Our_derived : Ultimate_base {
private:
    // Make this method private, so the derived class of this class
    // can't  invoke this implementation
    void do_foo() {
        Ultimate_base::do_foo();
        std::cout << " Our_derived::foo";
    }
};
struct Derive_from_derive : Our_derived {
private:
    void do_foo() {
        // You can't call below code
        // vvvvvvvvvvvvvvvvvvvvvv
        // Our_derived::do_foo();
        std::cout << " Derive_from_derive::foo";
    }
};
// This class is marked final by making its destructor private
// of course, from C++11, you have new keyword final
struct Derive_with_private_dtor : Ultimate_base {
private:
    ~Derive_with_private_dtor() {}
};
// You can't have below class because its destructor needs to invoke
// its direct base class destructor
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
/* 
struct Derive_from_private_dtor : Derive_with_private_dtor {
};
*/
int main() {
    std::unique_ptr<Ultimate_base> p = std::make_unique<Our_derived>();
    p->foo();
    p = std::make_unique<Derive_from_derive>();
    p->foo();
    p.reset(new Derive_with_private_dtor);
}

根据我对继承概念的理解,如果类Derived是公共类Base,那么你可以用Base做的任何事情也可以用Derived做。

这不是继承的真正概念;这就是多态性的概念。特别地,这是一个利斯科夫替代原理的陈述。但是在c++中,继承可以用于多种用途,而不仅仅是多态性。实际上,只要基类具有非虚方法或数据成员,就可以使用它将实现或状态注入派生类,而不仅仅是为了多态。如果基类没有虚方法和虚析构函数,则不应该(不能)以多态方式使用该类。您可以使用既不会实例化基类,也不会使用指向基类的指针的基类。下面是一个例子:

template <class T>
struct Foo {
    T double_this() { return 2 * static_cast<T&>(*this).data; }
    T halve_this() { return 0.5 * static_cast<T&>(*this).data; }
};

这个奇怪的类是做什么的?好吧,它可以用来注入一些接口到任何类的数据成员称为data(和一个适当的构造函数):

struct Bar : Foo<Bar> {
    Bar(double x) : data(x) {}
    double data;
};

现在Bardoublehalve方法。这种模式称为奇怪循环模板模式(CRTP)。请注意,我们永远不会实例化Foo<Bar>或拥有指向它的指针。我们只使用它提供的接口,非多态的。

现在,假设有人想使用Foo,但只希望double在他们的接口,而不是halve。在这种情况下,在派生类中将halve设置为私有是完全有效的。毕竟,派生类只是一些特殊的、非多态的类型,除了作者想要的/文档之外,它不需要遵守任何接口。

注意,当使用CRTP时,基类通常会提供公共函数,并且您通常会公开继承。在这个用例中,CRTP的全部优点是您可以直接注入接口;如果您打算将方法设为私有或私有继承,那么最好将Foo<Bar>设为Bar的成员。因此,在这种特殊情况下,更常见的是将公共内容变为私有内容,而不是相反。