从派生类对象调用基类方法

52

如何在派生类对象中调用被覆盖的基类方法?

class Base{
  public:
    void foo(){cout<<"base";}
};

class Derived:public Base{
  public:
    void foo(){cout<<"derived";}
}

int main(){
  Derived bar;
  //call Base::foo() from bar here?
  return 0;
}

相关内容:https://dev59.com/YEzSa4cB1Zd3GeqPnYvn - Vaughn Cato
13
如果一个函数没有被声明为虚函数,那么它就不会被覆盖(overridden),相反,派生类中的同名函数会_隐藏_(hide)基类中的函数。 - dyp
1
这看起来非常像一个设计问题。如果Derived有它自己的foo版本,那么很有可能它并没有被设计成在类外部调用基础版本时能正确地运行。 - Pete Becker
可能是重复的问题:如何从派生类函数中调用父类函数? - jww
4个回答

79
你可以使用限定标识符来调用基类的函数:
#include <iostream>

class Base{
  public:
    void foo(){std::cout<<"base";}
};

class Derived : public Base
{
  public:
    void foo(){std::cout<<"derived";}
};

int main()
{
  Derived bar;
  //call Base::foo() from bar here?
  bar.Base::foo(); // using a qualified-id
  return 0;
}

[也修正了一些原始问题的拼写错误。]

(*) 访问限制仍然适用,并且基类可能会有歧义。


如果 Base::foo 不是 virtual,那么 Derived::foo 就不会 覆盖 Base::foo。相反,Derived::foo 隐藏Base::foo。这种区别可以在以下示例中看到:

struct Base {
   void foo()         { std::cout << "Base::foo\n"; }
   virtual void bar() { std::cout << "Base::bar\n"; }
};

struct Derived : Base {
   void foo()         { std::cout << "Derived::foo\n"; }
   virtual void bar() { std::cout << "Derived::bar\n"; }
};

int main() {
    Derived d;
    Base* b = &d;
    b->foo(); // calls Base::foo
    b->bar(); // calls Derived::bar
}
(Derived::bar的签名与Base::bar兼容时,即使不使用virtual关键字,Derived::bar也会隐式地成为虚函数。) 限定符标识符(qualified-id)可以采用X :: Y:: Y的形式。冒号前面的部分指明我们要在哪里查找标识符Y。在第一种形式中,我们首先查找X,然后从X的上下文中查找Y。在第二种形式中,我们在全局命名空间中查找Y非限定符标识符(unqualified-id)不包含::,因此不指定查找名称的上下文。
在表达式b->foo中,bfoo都是非限定符标识符b在当前上下文中查找(在本例中是main函数)。因为b->foo具有类成员访问的形式,所以我们从b的类型(或者说*b的类型)的上下文中查找foo。因此,我们从Base的上下文中查找foo。我们将找到在Base内部声明的成员函数void foo(),我将其称为Base::foo
对于foo,我们现在完成了,并调用Base::foo
对于b->bar,我们首先找到Base::bar,但它被声明为虚函数。因为它是虚函数,我们执行虚分派。这将调用指向对象b类型的类层次结构中的最终覆盖函数。因为b指向一个类型为Derived的对象,所以最终的覆盖函数是Derived::bar
Derived的上下文中查找名称foo时,我们将找到Derived::foo。这就是为什么说Derived::foo隐藏Base::foo的原因。诸如d.foo()或在Derived的成员函数内部使用简单的foo()this->foo()的表达式将从Derived的上下文中查找。

使用限定符id时,我们明确声明要查找名称的上下文。表达式Base::foo表示我们想要从Base的上下文中查找名称foo(例如,它可以找到Base继承的函数)。 此外,它禁用了虚拟派发。

因此,d.Base::foo()将找到Base::foo并调用它; d.Base::bar()将找到Base::bar并调用它。


有趣的事实:纯虚函数可以有实现。它们不能通过虚拟派发进行调用,因为它们需要被覆盖。但是,您仍然可以使用限定符id调用它们的实现(如果有)。

#include <iostream>

struct Base {
    virtual void foo() = 0;
};

void Base::foo() { std::cout << "look ma, I'm pure virtual!\n"; }

struct Derived : Base {
    virtual void foo() { std::cout << "Derived::foo\n"; }
};

int main() {
    Derived d;
    d.foo();       // calls Derived::foo
    d.Base::foo(); // calls Base::foo
}
请注意,访问说明符在类成员和基类中的应用会影响您是否可以使用限定符调用派生类型对象上的基类函数。
例如:
#include <iostream>

struct Base {
public:
    void public_fun() { std::cout << "Base::public_fun\n"; }
private:
    void private_fun() { std::cout << "Base::private_fun\n"; }
};

struct Public_derived : public Base {
public:
    void public_fun() { std::cout << "Public_derived::public_fun\n"; }
    void private_fun() { std::cout << "Public_derived::private_fun\n"; }
};

struct Private_derived : private Base {
public:
    void public_fun() { std::cout << "Private_derived::public_fun\n"; }
    void private_fun() { std::cout << "Private_derived::private_fun\n"; }
};

int main() {
    Public_derived p;
    p.public_fun();        // allowed, calls Public_derived::public_fun
    p.private_fun();       // allowed, calls Public_derived::public_fun
    p.Base::public_fun();  // allowed, calls Base::public_fun
    p.Base::private_fun(); // NOT allowed, tries to name Base::public_fun

    Private_derived r;
    r.Base::public_fun();  // NOT allowed, tries to call Base::public_fun
    r.Base::private_fun(); // NOT allowed, tries to name Base::private_fun
}

可访问性与名称查找正交。因此名称隐藏对其没有影响(您可以在派生类中省略public_funprivate_fun,并获得限定标识符调用的相同行为和错误)。

顺便说一下,在p.Base::private_fun()中的错误与r.Base::public_fun()中的错误不同: 第一个错误已经无法引用名称Base::private_fun(因为它是一个私有名称)。第二个错误无法将rPrivate_derived&转换为Base&以用作this指针(基本上是这样)。这就是为什么第二个错误在Private_derived内部或Private_derived的友元中有效。


你的派生类foo隐藏了基类foo。在这种情况下,使用限定符id语法也可以工作吗? - nurabha
@nurabha,您能详细说明一下吗?我不太明白您的问题是什么。 - dyp
实际上,派生类的foo并没有覆盖基类的foo,而是被隐藏了。如果你用一个基类指针指向派生对象,并使用这个指针调用foo,那么基类的foo将会被输出。Base* basePtrToDerived = &bar; basePtrToDerived->foo(); - nurabha
1
@nurabha 是的。将类型转换为基类是另一种调用 Base::foo 的方式。使用限定符标识符调用基类函数与该函数在派生类中发生的任何变化无关 - 它可以被隐藏,可以被重写,可以通过使用 using 声明变为私有,使用限定符标识符时直接访问基类函数。 - dyp

22

首先,Derived 应该继承自 Base。

 class Derived : public Base{

话虽如此

首先,你可以在派生类中不使用foo

class Base{
  public:
    void foo(){cout<<"base";}
};

class Derived : public Base{

}

int main(){
  Derived bar;
  bar.foo() // calls Base::foo()
  return 0;
}

其次,您可以使Derived :: foo调用Base :: foo。

class Base{
  public:
    void foo(){cout<<"base";}
};

class Derived : public Base{
  public:
    void foo(){ Base::foo(); }
                ^^^^^^^^^^
}

int main(){
  Derived bar;
  bar.foo() // calls Base::foo()
  return 0;
}

第三种方法是使用Base::foo的限定id

 int main(){
    Derived bar;
    bar.Base::foo(); // calls Base::foo()
    return 0;
 }

3
我认为这个答案比被采纳的答案更有帮助。 - user23573

4

考虑一开始就将foo()设置为虚函数。

class Base {
public:
    virtual ~Base() = default;

    virtual void foo() { … }
};

class Derived : public Base {
public:
    virtual void foo() override { … }
};

然而,以下方法可解决这个问题:
int main() {
    Derived bar;
    bar.Base::foo();
    return 0;
}

最好将其编写为 bar.Base::foo() - Mankarse
1
@Mankarse 今天我学到了这是可能的。 - user142019
如果你将foo()设置为虚函数,仍需要调用bar.Base::foo();来访问基类方法,对吗? - Xun Yang
@XunYang 是的,你需要。虚拟性在这种情况下是无关紧要的,但我认为值得一提。 - user142019
好的,谢谢!实际上我正在使用一堆库,它们都使用一个非常通用的函数名 void Base1::begin(){\\initialization}, class Derived:public Base1, public Base2{...}; void Derived::begin(){\\initialization},所以架构很遗憾超出了我的能力范围 :( 只是在这里尝试进行一些 hackish 测试 :) - Xun Yang
您的派生类中使用的虚拟关键字是多余的。 - nurabha

0

一个重要的[附加]说明:如果发生名称隐藏,您仍然会遇到编译错误。

在这种情况下,可以使用using关键字或使用限定符。此外,还可以参考this answer

#include <iostream>

class Base{
  public:
    void foo(bool bOne, bool bTwo){std::cout<<"base"<<bOne<<bTwo;}
};

class Derived : public Base
{
  public:
    void foo(bool bOne){std::cout<<"derived"<<bOne;}
};

int main()
{
  Derived bar;
  //bar.foo(true,true);      // error:    derived func attempted
  bar.foo(true);             // no error: derived func
  bar.Base::foo(true,true);  // no error: base func, qualified
  return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接