为什么允许通过基类的指针调用派生类的私有虚方法?

时间:2011-02-14 10:51:33

标签: c++ inheritance virtual access-specifier

# 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;
}

此代码正常工作并打印B :: f()。我知道它是如何工作的,但为什么允许这个代码?

6 个答案:

答案 0 :(得分:14)

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

至于为什么B级被允许使用私有功能覆盖 - 我不确定。当然B违反了它从A继承所暗示的接口,但一般来说C ++语言并不总是强制继承接口,所以它的Just Plain Wrong并不意味着C ++会阻止你。

所以我猜这个类B可能有一些用例 - 替换仍然适用于动态多态,但静态B不能替代A(例如,可以有调用f的模板,可以使用A作为参数,但不能使用B作为参数)。可能存在这种情况,这正是您想要的。当然,这可能只是其他一些考虑因素的意外结果。

答案 1 :(得分:2)

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

如果A的设计者想要使其无法访问,则应将其标记为受保护或私有。

答案 2 :(得分:0)

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

干杯!

答案 3 :(得分:0)

除了史蒂夫的回答:

  • B公开地源于A.这意味着Liskov可替代性
  • 覆盖f是私有的似乎违反了这个原则,但实际上它并不一定 - 你仍然可以使用B作为A而不会妨碍代码,所以如果f的私有实现仍然可以用于B,没问题
  • 你可能想要使用这种模式是B应该是Liskov可替代A,但B也是另一个层次结构的根,它与A之间没有真正相关(以Liskov可替代的方式),其中f不再是公共界面。换句话说,通过指向B的指针使用从B派生的C类将隐藏f。
  • 然而,这实际上是不太可能的,从私人或受保护的方式派生B可能更好。

答案 4 :(得分:0)

函数访问控制检查发生在c ++函数调用的后期。 高级别的顺序将类似于名称查找,模板参数推断(如果有),重载解析,然后是访问控制(公共/保护/私有)检查。

但是在你的代码片段中,你使用了一个指向基类的指针,而基类中的函数f()确实是公共的,这就像编译器在编译时可以看到的那样,因此编译器肯定会让你的代码段通过。

A *ptr = new B;
ptr->f();

但是所有这些都是在编译时发生的,所以它们确实是静态的。虚拟功能调用通常由vtable&amp; vpointer是在运行时发生的动态内容,因此虚函数调用与访问控制正交(虚函数调用在访问控制之后发生),这就是为什么对f()的调用实际上结束了B :: f(),无论访问控制是私有的

但是如果你尝试使用

B* ptr = new B;
ptr->f()

尽管vpointer&amp; vtable,编译器不允许它在编译时编译。

但如果你尝试:

B* ptr = new B;
((static_cast<A*>(ptr))->f();

这样可以正常工作。

答案 5 :(得分:-1)

非常类似于Java,在C ++中,您可以提高方法的可见性,但不会降低它。