17
# 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()。我知道它是如何工作的,但为什么允许使用此代码?

4

6 回答 6

14

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

至于为什么允许 B 类完全使用私有函数覆盖 - 我不确定。当然 B 违反了从 A 继承所隐含的接口,但通常 C++ 语言并不总是强制接口继承,所以它只是完全错误的事实并不意味着 C++ 会阻止你。

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

于 2011-02-14T10:56:57.183 回答
2

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

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

于 2011-02-14T11:01:58.693 回答
0

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

干杯!

于 2011-02-14T10:56:20.013 回答
0

除了史蒂夫的回答:

  • B 是从 A 公开派生的。这意味着 Liskov 可替代性
  • 将 f 重写为私有似乎违反了该原则,但实际上并不一定 - 您仍然可以将 B 用作 A 而不会妨碍代码,因此如果 f 的私有实现对于 B 仍然可以,没有问题
  • 您可能想要使用此模式,B 应该是 Liskov 可替代 A,但 B 也是另一个与 A 不真正相关(以 Liskov 可替代方式)的层次结构的根,其中 f 不再是公共接口的一部分. 换句话说,从 B 派生的类 C,通过指向 B 的指针使用,将隐藏 f。
  • 然而,这实际上不太可能,从 A 派生出 B 可能是一个更好的主意。
于 2011-02-14T12:31:25.383 回答
0

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

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

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

但是以上所有这些都是在编译时发生的,所以它们实际上是静态的。虽然通常由 vtable 和 vpointer 提供支持的虚函数调用是在运行时发生的动态内容,因此虚函数调用与访问控制正交(虚函数调用发生在访问控制之后),这就是对 f() 的调用实际上结束的原因 B:: f() 不管访问控制是私有的。

但是如果你尝试使用

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

尽管有 vpointer 和 vtable,这不会通过,编译器将不允许它在编译时编译。

但如果你尝试:

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

这会很好用。

于 2012-10-10T17:06:40.613 回答
-1

与 Java 非常相似,在 C++ 中,您可以增加方法的可见性,但不能降低它。

于 2011-02-14T11:04:16.460 回答