为什么我可以通过指向派生对象的基类指针访问派生的私有成员函数?

时间:2010-08-31 16:08:49

标签: c++ inheritance polymorphism access-modifiers

#include<iostream>

using namespace std;
class base
{
public:
    virtual void add() {
        cout << "hi";
    }
};

class derived : public base
{
private:
    void add() {
        cout << "bye";
    }
};

int main()
{
    base *ptr;
    ptr = new derived;
    ptr->add();
    return 0;
}

输出为bye

我对如何实现它没有任何问题。我理解你使用vtable和派生的vtable包含新的add()函数的地址。但是当我尝试在类外部访问它时,add()是私有的,不应该编译器生成错误吗?不知怎的,这似乎不对。

3 个答案:

答案 0 :(得分:33)

add()仅在derived中是私有的,但您拥有的静态类型base* - 因此适用base的访问限制。
通常,您甚至无法在编译时知道指向base的指针的动态类型是什么,它可以是例如根据用户输入进行更改。

这是 C ++03§11.6

  

虚函数的访问规则(第11节)由其声明确定,并且不受稍后覆盖它的函数规则的影响。
  [...]使用表达式的类型在调用点检查访问,该表达式用于表示调用成员函数的对象[...]。成员函数在定义它的类中的访问通常是未知的。

答案 1 :(得分:12)

访问修饰符,例如publicprivateprotected仅在编译期间强制执行。通过指向基类的指针调用函数时,编译器不知道指针指向派生类的实例。根据编译器可以从该表达式推断出的规则,此调用有效。

降低派生类中成员的可见性通常是语义错误。现代编程语言(如Java和C#)拒绝编译此类代码,因为在基类中可见的成员始终可以通过基指针在派生类中访问。

答案 2 :(得分:5)

为Georg的回答添加一点:

请记住,编译器无法控制,也无法保证派生类的任何内容。例如,我可以将我的类型发布到库中,并从一个全新的程序中派生出来。如何知道库编译器知道派生可能有不同的访问说明符?编译库时,派生类型不存在。

为了支持这一点,编译器必须在运行时知道访问说明符,并在尝试访问私有成员时抛出异常。