C++中的函数重载不需要使用'virtual'关键字

7
我有一个包含一些函数的类(没有任何虚函数),还有两个子类公开继承该类。在这两个子类中,我重写了基类的同一个函数。
在主函数中创建了三个类的对象(均位于同一文件中),我使用基类对象调用原始函数,使用派生类对象调用重写函数。
我本来期望所有3个函数调用都会运行基类的原始函数(因为我没有在代码中使用“virtual”关键字),但实际上我得到的是每个函数版本根据其定义的类而变化(3个不同的版本)。
我的 Base 和 Derived 类如下:
struct Base
{
   void foo();
};

struct Derived : Base
{
   void foo();
};

在主函数中:

int main()
{
   Derived d;
   d.foo();
}

我认为如果没有使用“virtual”,则d.foo()应该运行Base::foo()。

1
请展示相关的C++代码。我们无法真正理解你的问题。 - Basile Starynkevitch
3
你为什么不把代码展示给我们,而不是描述它呢?一个SSCCE(http://sscce.org/)会是理想的。 - NPE
没有代码,就不清楚您在问什么。 - David Heffernan
抱歉没有提供代码,这是我在工作中遇到的一件事情,我只记得在脑海中有什么没有运行好的主要想法,但没有代码可以提供。不过,这里曾经有一个名为“delnan”的评论,他说的是正确的,但不知为何已经消失了。 - theexplorer
请参见https://dev59.com/RnI95IYBdhLWcg3wxA8-。 - Vaughn Cato
显示剩余5条评论
3个回答

26

这不是 "覆盖" ... 也不需要。

struct Base
{
   void foo();
};

struct Derived : Base
{
   void foo();
};

int main()
{
   Derived d;
   d.foo();
}

如果我理解正确的话,您希望执行 Base::foo(),因为这些函数不是虚函数,因此一个函数不会覆盖另一个函数。

但是,在这里,您不需要虚函数分派:继承规则只是简单地表明,您将得到您在其上运行的对象类型的正确函数。

当您需要虚函数分派/覆盖时,情况略有不同:那是当您使用间接引用时:

int main()
{
   Base* ptr = new Derived();
   ptr->foo();
   delete ptr;
}
在上面的代码片段中,结果将会调用Base::foo(),因为表达式ptr->foo()不知道*ptr实际上是一个Derived。它只知道ptr是一个Base*
这就是添加virtual(同时使一个函数覆盖另一个函数)的地方,这会产生神奇的效果。

2
你的派生类是否应该从基类派生? - Brandon
@Lightness Races in Orbit:我也有一个问题 :) 希望没关系: 像这样创建一个 Base 对象数组: Base array[2]={derivedObj1, derivedObj2}; 即使在 Base 类中是虚拟的,array[0].foo() 也会运行 Base 的 foo() 而不是 Derived 的 foo()。 另一方面,像这样创建一个 Base 对象数组: Base *array[2]={new Derived(), new Derived()}; array[0].foo() 运行了 Derived 的 foo()... 我能让第一个选项像第二个选项一样多态工作吗? - theexplorer
@theexplorer:不,你正在对对象进行切片操作。它们实际上被复制到“Base”对象中进行存储。 - Lightness Races in Orbit
@Lightness Races in Orbit:好的,我明白了。谢谢。 - theexplorer

2

你不能重写不是虚拟的东西。非虚拟成员函数是根据实例对象的类型静态分派的。


这怎么可能呢? 方法重写是面向对象编程中的一种语言特性,它允许子类或子类提供一个已由其超类或父类提供的方法的特定实现。 执行的方法版本将由用于调用它的对象确定。 这就是当我不使用关键字“virtual”时得到的结果。 - Heil Programmierung

0

你可以通过将一个函数变成内联函数,间接地调用某些东西来“覆盖”该函数。类似于以下代码(在C++03中):

 class Foo;
 typedef int foo_sig_t (Foo&, std::string&);
 class Foo {
    foo_sig_t *funptr;
 public:
    int do_fun(std::string&s) { return funptr(*this,s); }
    Foo (foo_sig_t* fun): funptr(fun) {};
    ~Foo () { funptr= NULL; };
    // etc
 };

 class Bar : public Foo {
    static int barfun(Bar&, std::string& s) { 
       std::cout << s << std::endl;
       return (int) s.size();
    };
  public: 
    Bar () : Foo(reinterpret_cast<foo_sig_t*>)(&barfun)) {};
     // etc...
  };

然后:

  Bar b;
  int x=b.do_fun("hello");

官方上来说这不是虚函数重载,但看起来非常接近。然而,在我上面的Foo示例中,每个Foo实例都有自己的funptr,这不一定是由类共享的。但是所有的Bar实例都共享指向相同的barfunfunptr

顺便说一句,使用C++11 lambda 匿名函数(在内部实现为闭包)会更简单、更短。

当然,虚函数通常实际上是通过类似的机制来实现的:对象(带有一些virtual的东西)隐式地以一个隐藏字段(可能被命名为_vptr)开始,给出vtable(或虚方法表)。

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