为什么覆盖虚函数时不考虑访问限定符?

Why access qualifier is not considered when virtual function is overridden?

以下代码打印 "I'm B!"。这有点奇怪,因为 B::foo() 是私有的。关于A* ptr我们可以说它的静态类型是Afoo是public),它的动态类型是Bfoo是私有的).所以我可以通过指向 A 的指针调用 foo。但是这样我就可以访问 B 中的私有函数。可以算作封装违规吗?

由于访问限定符不是 class 方法签名的一部分,因此可能会导致这种奇怪的情况。为什么在覆盖虚函数时不考虑 C++ 中的访问限定符?我可以禁止这种情况吗?这个决定背后的设计原则是什么?

Live example.

#include <iostream>

class A
{
public:
    virtual void foo()
    {
        std::cout << "I'm A!\n";
    };
};

class B: public A
{
private:
    void foo() override
    {
        std::cout << "I'm B!\n";
    };
};

int main()
{
    A* ptr;
    B b;

    ptr = &b;

    ptr->foo();
}

publicprivate 等访问限定符是编译时特性,而动态多态性是运行时特性。

当调用 virtual 函数的 private 覆盖时,您认为在运行时应该发生什么?例外?

Can it be considered as encapsulation violation?

不,因为接口已经通过继承发布,所以不是。

用派生的 class 中的 private 函数覆盖基 class 中的 public virtual 函数是完全没问题的(并且可能是有意的)。 =17=]

你有很多问题,我会尽量一一解答。

为什么重写虚函数时不考虑 C++ 中的访问限定符?

因为在所有重载决议之后,编译器都会考虑访问限定符。 这种行为由标准规定。

例如,参见 on cppreference:

Member access does not affect visibility: names of private and privately-inherited members are visible and considered by overload resolution, implicit conversions to inaccessible base classes are still considered, etc. Member access check is the last step after any given language construct is interpreted. The intent of this rule is that replacing any private with public never alters the behavior of the program.

下一段描述了您的示例所展示的行为:

Access rules for the names of virtual functions are checked at the call point using the type of the expression used to denote the object for which the member function is called. The access of the final overrider is ignored.

另请参阅 this answer 中列出的操作顺序。

我可以禁止这种情况吗?

没有

而且我认为您永远无法这样做,因为这种行为并不违法。

这个决定背后的设计原则是什么?

澄清一下:这里的 "decision" 是指编译器在重载解析后检查访问限定符的处方。 简短的回答:防止在更改代码时出现意外。

有关更多详细信息,我们假设您正在开发一些 CoolClass,看起来像这样

class CoolClass {
public:
  void doCoolStuff(int coolId); // your class interface
private:
  void doCoolStuff(double coolValue); // auxiliary method used by the public one
};

假设编译器可以根据 public/private 说明符进行重载解析。然后下面的代码将成功编译:

CoolClass cc;
cc.doCoolStuff(3.14); // invokes CoolClass::doCoolStuff(int)
  // yes, this would raise the warning, but it can be ignored or suppressed 

然后在某些时候你发现你的私有成员函数实际上对 class 客户端有用,并将其移动到 "public" 区域。这会自动 更改先前存在的客户端代码 的行为,因为现在它调用 CoolClass::doCoolStuff(double).

因此,应用访问限定符的规则是以 不允许 允许这种情况的方式编写的,因此您会在最开始出现 "ambiguous call" 编译器错误开始。出于同样的原因,虚函数也不是特例(参见 this answer)。

可以算作封装违规吗?

不是真的。 通过将指向 class 的指针转换为指向其基数 class 的指针,您实际上是在说:"Herewith I would like to use this object B as if it's an object A" - 这是完全合法的,因为继承意味着 "as-is" 关系。

那么问题来了,你的例子能不能算作违反基地class规定的契约?好像是的,可以。

请参阅 this question 的答案以了解其他解释。

P.S.

不要误会我的意思,所有这些并不意味着您不应该使用私有虚函数。相反,它通常被认为是一种好的做法,请参阅 this thread。但它们从根本上就应该是私有的 class。所以,底线是,你不应该使用私有虚函数来破坏 public 契约。

P.P.S。 ...除非你故意想通过指向接口/基class的指针强制客户端使用你的class。但是有更好的方法,我相信对这些的讨论超出了这个问题的范围。