【问题标题】:Public virtual function derived private in C++公共虚函数派生在 C++ 中的私有
【发布时间】:2021-10-22 11:36:57
【问题描述】:

我试图弄清楚当派生类将虚函数声明为私有时会发生什么。以下是我写的程序

#include <iostream>
using namespace std;
class A
{
    public:
        virtual void func() {
        cout<<"A::func called"<<endl;
    }
    private:
};
class B:public A
{
    public:
    B()
    {
        cout<<"B constructor called"<<endl;
    }
    private:
    void func() {
        cout<<"B::func called"<<endl;
    }
};
int main()
{
    A *a = new B();
    a->func();
    return 0;
}

令人惊讶的是(对我来说)输出是:

B constructor called
B::func called

这是否违反了为该功能设置的私有访问权限。这是预期的行为吗?这是标准的解决方法还是漏洞?通过 VTABLE 解析函数调用时是否绕过访问级别?

对此行为的任何见解都会非常有帮助。

进一步提到,私有覆盖虚拟成员会阻止其他类继承它。即使这样也有问题。修改上述程序以包含:

class C: public B
{
    public:
    void func() {
        cout<<"C::func called"<<endl;
    }
};

和主测试程序:

int main()
{
    A *a = new C();
    a->func();
    return 0;
}

输出是:

C::func called

【问题讨论】:

  • 我只是想指出一个不能在java中做到这一点。当一个方法被覆盖时,它的访问级别必须等于或高于被覆盖的方法的公共访问级别。原因是您必须确保可以从可以调用超类版本的所有上下文中调用子类版本的方法。我觉得有趣的是,这在 C++ 中是允许的。
  • 好吧..可以说如果你一心想 C++,C++ 允许你上吊。

标签: c++ inheritance virtual-functions


【解决方案1】:

这是定义明确的行为。如果 aB* 这将无法编译。原因是成员访问由编译器静态解析,而不是在运行时动态解析。许多 C++ 书籍建议您避免这样的编码,因为它会使经验不足的编码人员感到困惑。

【讨论】:

  • 编译器静态解析成员访问是什么意思? vtable 的全部意义在于编译器不知道在编译时要调用哪个函数……所以它必须在运行时查找它。你能澄清一下吗?
  • @Tom:在编译时检查程序的语义。您正在使用 A* 调​​用 func() ,这是一个有效的调用。编译器无法知道此调用在运行时解析为私有成员函数调用。在运行时,没有私有或公共的概念,它只是一个函数调用。如果您使用 B* 而不是 A*,则对 func() 的调用将无效,编译器会将其标记为错误。
  • @Naveen:谢谢。我想我忘记了编译器在运行时没有保留该访问信息……对我来说,这似乎是不受欢迎的行为。特别是因为您可以将 B 转换为 A 以尝试访问私有函数。现在这是有道理的......我只是不喜欢它:-)。
  • 说“避免这样的编码,因为它会使经验不足的编码人员感到困惑”意味着如果不是“经验不足的编码人员”,您可能会想要这样做。有吗?
  • @Laurence:我记得读过一些关于为什么要这样做,以及避免这样做的建议。从那以后,我忘记了这可能有用的原因。
【解决方案2】:

好吧,您正在调用A::func(),即public,尽管在B 对象中它被B::func() 覆盖。这是一种常见的模式,具有以下含义:

  • func 不打算在派生的B 对象上调用

  • func不能在派生自B的类中被覆盖

【讨论】:

  • 如果你说“func 不打算在派生的 B 对象上调用”,那它为什么会起作用?
  • @Tom:调用 A *a = new B() 有效,因为您调用的是 A::func()。调用 B *b = new B() 无法编译,因为您在 B 之外调用 private B::func()。
  • ... 在 B 或其朋友之外,即。
  • @laalto:我不明白这一点。但似乎 Ashwin 是在说 a->func 正在调用 B::func。这似乎打破了访问级别,因为每当我想调用私有 B::func 时,我都可以将 B 强制转换为 A,然后调用 func()。好像我不应该那样做。似乎您是说在子类中将访问级别更改为私有的目的是使子子类不能覆盖 func() 并且如果您有静态类型 B 的东西,则不能调用 func( )。但是,如果您可以通过强制转换来解决这个问题,该方法仍然不是真正的私有 :-(。
  • @Tom:C++ 和直觉并不总是齐头并进的。
【解决方案3】:

行为是正确的。每当您将函数声明为“虚拟”时,都会指示编译器生成虚拟调用,而不是直接调用此函数。每当您在后代类中重写虚函数时,您都指定了该函数的行为(您不会更改那些依赖“父”接口的客户端的访问模式)。

更改后代类中虚函数的访问模式意味着您希望对那些直接使用后代类的客户端(依赖“子”接口)隐藏它。

考虑这个例子:

void process(const A* object) {
   object->func();
}

“进程”函数依赖于父接口。它应该适用于任何从 A 公开派生的类。您不能从 A 公开派生 B(说“每个 B 都是 A”),而是隐藏其接口的一部分。那些期望“A”的人必须得到一个功能齐全的“A”。

【讨论】:

  • 我同意。只是将 Private 函数转换为 public 的编译器似乎是不可接受的。但真正的“你不能从 A 公开派生 B(说“每个 B 都是 A”),而是隐藏其接口的一部分。那些期望“A”的人必须收到一个功能齐全的“A”。谢谢你的解释。
  • 谢谢,SadSido。您似乎是唯一一个解决“公共衍生”问题的人。我实际上忘记了不仅仅是公共继承,因为我只使用公共继承。允许这种行为的是公共继承的性质——这更有意义。我认为这是最好的解释。谢谢。
  • 谢谢大家。然而,关于这个问题,有一个问题让我很困扰。我可以有一个指向子对象的指针,并通过将其静态转换为父对象来调用它的私有方法......好吧,我猜,在这种情况下,静态转换意味着“依赖父接口”......
  • 我可能会补充一点,要求编译器禁止通过 a->func 调用 B 的私有虚函数几乎是不可能的。在编译时检查是否违反访问权限,但此访问发生在运行时。即使您要使用运行时检查来填充可执行文件,是否真的希望程序的用户看到程序因访问冲突错误而停止?
  • 除此之外 - 考虑替代方案。如果派生类修改了基类接口,则依赖于接口的函数可能会崩溃。例如,假设 void doFunc (A* ptr) { ptr-&gt;func(); } 是在编写 A 时编写的。随后,B 扩展了 A 并修改了它的接口。由于B* IS-A A*,编译器允许我们传递 doFunc (&bObj)。如果允许 B 更改 func() 的访问模式,这将崩溃。实际上,使用接口编程是发生这种情况的原因。
猜你喜欢
  • 2016-10-08
  • 2010-10-03
  • 2015-02-21
  • 1970-01-01
  • 2012-11-17
  • 1970-01-01
  • 2015-04-16
  • 2017-06-08
相关资源
最近更新 更多