如果我重写了一个虚函数,我能调用基类的虚函数吗?

415

假设我有如下设置的类 FooBar

class Foo
{
public:
    int x;

    virtual void printStuff()
    {
        std::cout << x << std::endl;
    }
};

class Bar : public Foo
{
public:
    int y;

    void printStuff()
    {
        // I would like to call Foo.printStuff() here...
        std::cout << y << std::endl;
    }
};

如代码中所注释的那样,我想能够调用我正在覆盖的基类函数。在Java中有 super.funcname() 的语法。在C++中可行吗?


1
可能是从基类调用虚函数的重复问题。 - Vladimir F Героям слава
1
对于谷歌员工:请注意,您可能会遇到像我一样的问题,将其存储为非指针类成员变量。请参见我的答案:https://dev59.com/sW445IYBdhLWcg3wkLFG#42429076 我使用new/delete来解决问题。 - Andrew
装饰器设计模式就是基于这个的 :) - LRDPRDX
8个回答

537

在C++中,调用派生类方法时必须显式命名基类。这可以从派生类的任何方法中完成。覆盖是与同名方法的特殊情况。在Java中不存在多重继承,因此您可以使用super来唯一命名基类。C++语法如下:

class Bar : public Foo {
  // ...

  void printStuff() override {  // help the compiler to check
    Foo::printStuff(); // calls base class' function
  }
};

16
这样做是否存在可能的问题?这是不好的实践吗? - Robben_Ford_Fan_boy
43
@David:不,这是完全正常的做法,尽管它可能取决于你实际的类是否真正有用。只有在基类方法执行你想要发生的事情时才调用它;)。 - sth
29
当你的客户端被要求这样做时,这是一种“代码异味”(code smell)!(称为“调用super要求”,在这里查看:http://en.wikipedia.org/wiki/Call_super) - v.oddou
18
在有用的情况下调用父类是没有问题的。你链接的那个页面只涉及到继承中的一些潜在问题。它甚至自己说,在一般情况下调用父类没有任何问题:“请注意,要求调用父类是反模式。在真实代码中有很多例子,其中子类中的方法仍可能需要超类的功能,通常仅在增强父类功能时使用。” - sth
35
大多数人可能已经知道,但为了完整起见,请记住在构造函数和析构函数中永远不要这样做。 - TigerCoding
显示剩余11条评论

137

是的,

class Bar : public Foo
{
    ...

    void printStuff()
    {
        Foo::printStuff();
    }
};

这与Java中的super相同,但允许在具有多重继承时从不同的基类调用实现。

class Foo {
public:
    virtual void foo() {
        ...
    }
};

class Baz {
public:
    virtual void foo() {
        ...
    }
};

class Bar : public Foo, public Baz {
public:
    virtual void foo() {
        // Choose one, or even call both if you need to.
        Foo::foo();
        Baz::foo();
    }
};

7
这个答案比被选中的那个更好。谢谢。 - Mad Physicist
1
多重继承?哦,天啊! - lamino
C++太疯狂了,我处理它已经4天了,几乎快要崩溃了...如果我想象一下在C++中像在Kotlin中编写一些非常复杂的代码,我最好把我的爱好改成园艺。 - Renetik

84
有时候你需要在派生函数之外调用基类的实现...这仍然有效:
struct Base
{
    virtual int Foo()
    {
        return -1;
    }
};

struct Derived : public Base
{
    virtual int Foo()
    {
        return -2;
    }
};

int main(int argc, char* argv[])
{
    Base *x = new Derived;

    ASSERT(-2 == x->Foo());

    //syntax is trippy but it works
    ASSERT(-1 == x->Base::Foo());

    return 0;
}

2
请注意,为使此代码正常工作,您需要将x的类型设置为Base*并使用Base::Foo语法。 - TTimo

29

如果你在类中有很多函数需要这样做:

class Foo {
public:
  virtual void f1() {
    // ...
  }
  virtual void f2() {
    // ...
  }
  //...
};

class Bar : public Foo {
private:
  typedef Foo super;
public:
  void f1() {
    super::f1();
  }
};

如果您想重命名Foo,则以下内容可能会节省一些编写工作。


1
有趣的方式,但无法处理多重继承。 - Mad Physicist
7
如果有多个基类怎么办?无论如何,试图让 C++ 看起来像其他语言通常是愚蠢和徒劳的。只需要记住基类的名称,并用它来调用即可。 - underscore_d

6

如果你想从继承类中调用基类的函数,你可以在重载函数内部直接使用基类名称调用(例如 Foo::printStuff())。

代码如下:

#include <iostream>
using namespace std;

class Foo
{
public:
    int x;

    virtual void printStuff()
    {
         cout<<"Base Foo printStuff called"<<endl;
    }
};

class Bar : public Foo
{
public:
    int y;

    void printStuff()
    {
        cout<<"derived Bar printStuff called"<<endl;
        Foo::printStuff();/////also called the base class method
    }
};

int main()
{
    Bar *b=new Bar;
    b->printStuff();
}

您可以在运行时使用该类的对象(派生或基础)确定调用哪个函数。但是这要求您在基类中的函数必须标记为虚函数。

以下是代码:

#include <iostream>
using namespace std;

class Foo
{
public:
    int x;

    virtual void printStuff()
    {
         cout<<"Base Foo printStuff called"<<endl;
    }
};

class Bar : public Foo
{
public:
    int y;

    void printStuff()
    {
        cout<<"derived Bar printStuff called"<<endl;
    }
};

int main()
{

    Foo *foo=new Foo;
    foo->printStuff();/////this call the base function
    foo=new Bar;
    foo->printStuff();
}

3
如果存在多层继承关系,您可以指定直接的基类,即使实际实现位于更低的级别。
class Foo
{
public:
  virtual void DoStuff ()
  {
  }
};

class Bar : public Foo
{
};

class Baz : public Bar
{
public:
  void DoStuff ()
  {
    Bar::DoStuff() ;
  }
};

在这个例子中,类 Baz 指定了 Bar::DoStuff(),尽管类 Bar 没有包含 DoStuff 的实现。 Baz 不需要知道这一细节。
显然,最好的做法是调用 Bar::DoStuff 而不是 Foo::DoStuff,以防后来版本的 Bar 也覆盖此方法。

0

可以的。在子类中调用父类函数的C++语法是:

class child: public parent {
  // ...

  void methodName() {
    parent::methodName(); // calls Parent class' function
  }
};

了解更多关于函数覆盖的内容。


0

检查一下这个...

#include <stdio.h>

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

class Derived : protected Base {
public:
   virtual void gogo(int a) { printf(" Derived :: gogo (int) \n"); };
   void gogo1(int a) { printf(" Derived :: gogo1 (int) \n"); };
   virtual void gogo2(int a) { printf(" Derived :: gogo2 (int) \n"); };
   void gogo3(int a) { printf(" Derived :: gogo3 (int) \n"); };       
};

int main() {
   std::cout << "Derived" << std::endl;
   auto obj = new Derived ;
   obj->gogo(7);
   obj->gogo1(7);
   obj->gogo2(7);
   obj->gogo3(7);
   std::cout << "Base" << std::endl;
   auto base = (Base*)obj;
   base->gogo(7);
   base->gogo1(7);
   base->gogo2(7);
   base->gogo3(7);

   std::string s;
   std::cout << "press any key to exit" << std::endl;
   std::cin >> s;
   return 0;
}

输出

Derived
 Derived :: gogo (int)
 Derived :: gogo1 (int)
 Derived :: gogo2 (int)
 Derived :: gogo3 (int)
Base
 Derived :: gogo (int)
 Derived :: gogo1 (int)
 Base :: gogo2 (int)
 Base :: gogo3 (int)
press any key to exit

最好的方法是使用 base::function,就像 @sth 所说的那样。


正如在这个问题中所解释的那样,由于使用了protected继承,这种方法不起作用。要转换基类指针,必须使用公共继承。 - Darkproduct
有趣。阅读了这个答案之后,我认为使用protected继承,Derived从Base派生的事实只对类本身可见,而对外部不可见。 - Darkproduct

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