【问题标题】:Confusion regarding name hiding and virtual functions关于名称隐藏和虚函数的混淆
【发布时间】:2012-04-16 11:26:45
【问题描述】:

引用另一个so question

考虑代码:

class Base {
public: 
    virtual void gogo(int a){
        printf(" Base :: gogo (int) \n");
    };

    virtual void gogo(int* a){
        printf(" Base :: gogo (int*) \n");
    };
};

class Derived : public Base{
public:
    virtual void gogo(int* a){
        printf(" Derived :: gogo (int*) \n");
    };
};

int main(){

    // 1)       
    Derived * obj = new Derived ; 
    obj->gogo(7);  // this is illegal because of name hiding


    // 2)      
    Base* obj = new Derived ;
    obj->gogo(7); // this is legal
}

对于情况2)

调用obj->gogo(7) 在运行时解决。

因为obj->gogo(7) 是合法的。似乎暗示 Derived 的 vtable 包含 指向 virtual void gogo(int a) 应该被隐藏了。

我的困惑是,由于名称隐藏导致案例 1) 是非法的,那么如何在运行时解决 2) 中的调用

a) Derived 的 vtable 是否包含指向 gogo(int) 的指针。

b) 如果 a) 不为 True,是否对虚函数的调用解析继续到基类的 vtable。

【问题讨论】:

  • @AndersK 函数Base::gogo(int) 确实被Derived::gogo(int*) 隐藏了。但是Derived 类中的using Base::gogo; 语句可以解决这个特殊问题。
  • @MichaelWild 是的,我看到了我的错误。

标签: c++ virtual overriding name-hiding


【解决方案1】:

您混淆了虚函数调用和重载解析。

所有派生类都有包含所有虚函数的虚表,来自基类和任何其他自己的虚函数。这用于解决 runtime 的调用,如您的情况 2)。

在情况 1) 中,您在 编译时 收到重载解析错误。由于名称隐藏,Derived 类只有一个可调用函数。您唯一的选择是使用 int* 调用该函数。

【讨论】:

  • 不,没有错误。由于gogo() 的两个重载都在Base 中声明,因此覆盖一个不会隐藏另一个。
  • 这确实解释了上述行为。 c++ 标准(或任何书籍/文档)是否描述了同样的事情。我的意思是有可能获得引用。
  • @fizzbuzz - 在内部作用域中声明的名称总是隐藏在外部作用域中的名称。在这种情况下,派生类是内部作用域,基类是外部作用域。
  • @Bo "所有派生类都有包含所有虚函数的 vtables,来自基类和任何其他自己的虚函数" 对此有任何引用吗?
  • @hitesg - 不,标准中根本没有关于 vtables 的内容。它只是一个实现选项(目前所有已知的编译器都使用)。
【解决方案2】:

你想覆盖重载的函数,但隐藏规则不能这样工作。

“隐藏规则说,内部范围内的实体将具有相同名称的事物隐藏在外部范围内。”

注意,它具有不同的签名是无关紧要的,即gogo(int* a) 将隐藏所有gogo(whatever) 函数。仅当您的函数具有相同的名称、相同的签名和虚拟函数时才会发生覆盖(因此,只有 gogo(int* a) 会被覆盖)。

C++FAQ 书建议使用“调用非重载虚拟的非虚拟重载”。 (第 29.05 章)。基本上你在基类中创建非虚拟重载函数:

gogo(int a) and gogo(int* a)

分别调用虚函数:

虚拟gogo_i(int a)和虚拟gogo_pi(int* a)

并在 Derived 类中覆盖此虚拟。

【讨论】:

    【解决方案3】:

    基本上,函数重载只有在同名函数定义在同一个范围内时才会发生。现在,基类有自己的范围,派生类有自己的范围。

    因此,当您不在派生类中重新定义函数并调用该函数时,编译器会检查派生的范围,发现其中没有定义这样的函数。然后它检查基类的范围,发现函数并相应地将函数调用绑定到这个特定的定义。

    但是,当你用不同的签名重新定义函数时,编译器会将调用与这个函数匹配,看到不一致并简单地抱怨。

    您可以通过在派生类定义中添加“using Base::gogo;”来更改此行为。 我希望这可以解释。

    【讨论】:

      【解决方案4】:

      由于您将第二个obj 声明为Base*,vtable 为其提供了Base 的所有方法。虽然对于被Derived覆盖的虚方法,被覆盖的版本会被调用,但其他方法(或方法重载)仍然是Base中声明的那些。

      但是,如果您将指针声明为Derived*,vtable 将给它Derived 的方法,隐藏Base 中具有相同名称的方法。因此,obj->gogo(7); 将不起作用。同样,这也是非法的:

      Base* obj = new Derived();
      
      // legal, since obj is a pointer to Base, it contains the gogo(int) method.
      obj->gogo(7); 
      
      // illegal, it has been cast into a pointer to Derived. gogo(int) is hidden.
      (reinterpret_cast<Derived*>(obj))->gogo(7);
      

      这是合法的:

      Derived* obj = new Derived ;
      obj->Base::gogo(7); // legal.
      

      here

      【讨论】:

      • dynamic_cast 在您的示例中不是更合适吗?
      • 据我所知,这与实际的 vtable 查找无关,而是与静态类型和名称隐藏有关(参见 Bo Persson 的回答)。如果类型为Derived,则适用这些语义,如果类型为Base,则适用其他语义。
      猜你喜欢
      • 2015-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-25
      • 2015-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多