C++ 为什么允许调用派生类';通过基类指针的私有虚方法? #包括 使用名称空间std; 甲级 { 公众: 虚空f() { cout

C++ 为什么允许调用派生类';通过基类指针的私有虚方法? #包括 使用名称空间std; 甲级 { 公众: 虚空f() { cout,c++,inheritance,virtual,access-specifier,C++,Inheritance,Virtual,Access Specifier,您的基类正在为所有继承的子类定义接口。我不明白为什么它会阻止上述访问。您可以尝试从“B”派生一个类,并使用基类接口进行调用,这将导致错误 干杯!访问控制是在编译时执行的,而不是在运行时。通常,调用f()无法知道ptr指向的对象的运行时类型,因此不检查派生类的访问说明符。这就是允许调用的原因 为什么B类允许使用私有函数重写?我不确定。当然B违反了它继承的接口,但通常C++语言不总是强制接口的继承,所以事实上它只是简单错误,并不意味着C++会阻止你。 因此,我猜可能存在此类B的一些用例-替换仍然适用

您的基类正在为所有继承的子类定义接口。我不明白为什么它会阻止上述访问。您可以尝试从“B”派生一个类,并使用基类接口进行调用,这将导致错误


干杯!

访问控制是在编译时执行的,而不是在运行时。通常,调用
f()
无法知道
ptr
指向的对象的运行时类型,因此不检查派生类的访问说明符。这就是允许调用的原因

为什么B类允许使用私有函数重写?我不确定。当然B违反了它继承的接口,但通常C++语言不总是强制接口的继承,所以事实上它只是简单错误,并不意味着C++会阻止你。


因此,我猜可能存在此类B的一些用例-替换仍然适用于动态多态性,但静态上B不是a的替代品(例如,可以有调用
f
的模板,可以使用a作为参数,但不能使用B作为参数)。在某些情况下,这可能正是您想要的。当然,这可能只是出于其他考虑而产生的意外结果。

此代码是允许的,因为f在A的接口中是公共的。派生类不能更改基类的接口。(重写虚方法不会更改接口,也不会隐藏基的成员,尽管两者都可能会这样做。)如果派生类可以更改基的接口,则会违反


如果设计者想使F不能访问,则应该标记为保护或私有。

< P> >类似于java,C++中,除了史提夫的回答之外,可以增加方法的可见性,但不能降低它的可见性。

< P>
  • B公开来源于A,这意味着Liskov可替代性
  • 将f重写为私有似乎违反了这一原则,但实际上并不一定——您仍然可以使用B作为A,而不需要代码的阻碍,因此如果f的私有实现对B来说仍然可以,那么没有问题
  • 您可能希望使用此模式,因为B应该是Liskov可替代A的,但B也是另一个层次结构的根,该层次结构与A没有真正的关联(以Liskov可替代的方式),其中f不再是公共接口的一部分。换句话说,从B派生的类C通过指向B的指针使用,将隐藏f
  • 然而,这实际上是不太可能的,从私人或受保护的

函数访问控制检查发生在C++函数调用的后期。 高级中的顺序类似于名称查找、模板参数推断(如果有)、重载解析,然后是访问控制(public/protect/private)检查

但在代码段中,您使用了指向基类的指针,基类中的函数f()确实是公共的,这是编译器在编译时所能看到的,所以编译器肯定会让您的代码段通过

# include <iostream>
using namespace std;

class A
{
    public:
    virtual void f()
    {
        cout << "A::f()" << endl;
    }
};
class B:public A
{
    private:
    virtual void f()
    {
        cout << "B::f()" << endl;
    }
};
int main()
{
    A *ptr = new B;
    ptr->f();
    return 0;
}
但是以上所有这些都是在编译时发生的,所以它们实际上是静态的。虽然通常由vtable和vpointer支持的虚拟函数调用是动态的,在运行时发生,所以虚拟函数调用与访问控制正交(虚拟函数调用发生在访问控制之后),这就是为什么对f()的调用实际上结束了B::f()无论如何,访问控制是私有的

但是如果你尝试使用

A *ptr = new B;
ptr->f();
尽管vpointer&vtable存在漏洞,但这不会通过,编译器将不允许它在编译时编译

但如果你尝试:

B* ptr = new B;
ptr->f()
B*ptr=新B;
((静态_-cast(ptr))->f();

这是很好的。

我不知道java,但是b::f确实隐藏了A::f,在爪哇,你不能在派生类中生成继承的方法,它不会编译。它不是LSP违背,因为B对象可以在A CAN中到处使用。这样的上下文触发隐式派生的基转换,可能会将额外的PAR切割。在这样的转换之后,<代码> b::f>代码>被访问为<代码>::f>代码>,这是公共的.@ MsAlter:C++中有两种不同的替换,因为至少有(至少)C++中的两种不同的多态性。我们可以单独地评估LSP是否适用于每个。如果在一个函数调用中,B对象是通过值传递的,它使用模板参数推导来从模板中实例化函数,那么它很可能不会被切片,并且确实会访问<代码> f>代码>作为<代码> b::f< /C>满足A满足的所有概念,因此对象仅在类型被剥离后才可替换(例如,使用A*,或转换为A).LSP是一个约束命名一致性的OOP原则。模板上下文不是正常的OOP上下文。它是泛型编程,通常忽略继承。相反,它使用结构一致性。因此OOP中的LSP原则不适用于模板参数。而且,仅仅因为B可转换为a,也不能使B成为B对象替换C++中的对象。A类型的对象可以传递给一个函数,该函数使用<代码> A& <代码>,或者一个采用<代码> C>代码>,其中转换为 C<代码>代码。(除了使用
A&A_ref=b_obj
)传递的对象不同。@MSalters:如果您将LSP限制为仅适用于动态多态性(将指针或引用显式键入为基类)那么好吧,但如果你声称派生->基片在任何方面都是相关的,那么我认为模板推断也应该是相关的
B* ptr = new B;
((static_cast<A*>(ptr))->f();