覆盖具有不同返回类型的成员函数

30

考虑下面的示例:

#include <iostream>

using namespace std;

class base
{
   public:
      virtual int func()
      {
         cout << "vfunc in base class\n";
         return 0;
      }
};

class derived: public base
{
   public:
      double func()
      {
         cout << "vfunc in derived class\n";
         return 0;
      }
};

int main()
{
   base *bptr = new derived;
   bptr->func();

   return 0;
}
编译器为上述代码提供了一个错误,指出重写的函数存在冲突类型。为什么在派生类中无法用不同的返回类型覆盖函数?
我认为,为了重写一个函数,必须在派生类中重新定义基类的虚拟方法。要重新定义一个方法,方法的签名必须相同。由于返回类型不是签名的一部分,即使返回类型有所不同,方法仍将被重新定义。对于上面的代码,虚拟函数“func”在派生类中以不同的返回类型重新定义。但是编译器抛出了错误。我的理解正确吗?

1
为了澄清,是哪个编译器给你报错了? - Sion Sheevok
@SionSheevok,GCC至少可以:http://codepad.org/z7rXpCeK - bdonlan
@SionSheevok:我正在使用gcc 3.4.6。 - nitin_cherian
点头 我并没有暗示它不会报错,只是好奇编译器具体想要传达什么信息。 - Sion Sheevok
5个回答

38

覆盖(Overriding)基本上意味着在运行时,根据指针所指向的实际对象,将调用基类方法(Base class method)或派生类方法(Derived class method)之一。
这意味着:
即:在没有改变调用代码的情况下,可以用调用派生类方法的方式替换调用基类方法的任何地方。

为了实现这一点,唯一可能的方法是限制重写虚拟方法的返回类型与基类相同或派生自基类的类型(协变返回类型),并且标准强制执行此条件。

如果没有上述条件,它会留下一个窗口来通过添加新功能来破坏现有代码。


7
作为“那个类型(协变返回类型)”的派生类型,这对我理解很关键。解释得非常好。 :) - nitin_cherian

14
为了重写虚函数,返回值必须完全相同*。在这里,C++不会自动转换doubleint之间的类型-毕竟,在从派生类指针调用时,它怎么知道你想要的返回类型呢?请注意,如果您更改部分签名(参数、const性等),则也可以更改返回值。

*-严格来说,它必须是“协变的”。这意味着你返回的类型必须是父函数返回类型的子集。例如,如果父类返回一个base *,你可以返回一个derived *。由于derived同时也是base,所以编译器允许你以这种方式进行覆盖。但是你不能返回完全不相关的类型,比如intdouble;尽管有隐式转换,但编译器不会让你这样做覆盖。


请注意,如果您更改签名的一部分(参数、常量性等),那么您也可以更改返回值。如果我这样做,那么函数就不会被覆盖了,对吗? - nitin_cherian
3
我认为你的意思是“完全相同或者是一个子类”。 - Nemo
1
虚函数允许协变返回类型。 - Alok Save
@LinuxPenseur,没错,如果函数签名改变,该函数不会被覆盖。 - bdonlan
@Als:为了强调,我们可以提供给您我们时尚的斜体,还有最新消息,粗体 - Kerrek SB
@KerrekSB:我知道,只是匆忙打了出来 :) 然后去添加了自己的答案,等我看到你的评论并意识到这个错误时,已经太晚编辑了。 - Alok Save

10

参见此问题。简而言之,只有当类型是协变的时,您才能使用不同的返回类型覆盖虚函数。


类型不是协变的,重写是。 - Kerrek SB
不,只是你的解释有点特立独行。 - Kerrek SB

2

如果您想进行重写,应尝试使用模板。

请参见以下内容:

#include <iostream>

using namespace std;

class base
{
   public:
      template<typename X> X func()
      {
         cout << "vfunc in base class\n";
         return static_cast<X>(0);
      }  
};    

class derived: public base
{
   public:
      template<typename X> X func()
      {
         cout << "vfunc in derived class\n";
         return static_cast<X>(2);
      }  
};    

int main()
{
   derived *bptr = new derived;
   cout << bptr->func<int>() << endl;
   cout << dynamic_cast<base*>(bptr)->func<int>() << endl;

   derived *bptr2 = new derived;
   cout << bptr->func<double>() << endl;
   cout << dynamic_cast<base*>(bptr)->func<int>() << endl;


   return 0;
}

当然,您不需要以这种方式在两个不同的类中声明它,您可以这样做:
class base
{
   public:
      int func()
      {
         cout << "vfunc in base class\n";
         return 0;
      }  

      double func(){
        cout << "vfunc for double class\n";
        return 2.;

      }
};

-2

由于签名相同且唯一的区别是返回值,因此无法进行覆盖。覆盖的基本目的是多态性,但在上述示例中不可能实现。


2
这里的签名是相同的。函数仅在返回类型上有所不同,而返回类型不是签名的一部分。 - nitin_cherian
1
@LinuxPenseur 谢谢你告诉我,那正是我想表达的意思。 - kvk
当覆盖一个方法时,只要这个改变不与声明的方法冲突(类型是协变的),就可以改变返回类型。这不会破坏多态性,因为覆盖的协变类型也是方法声明的有效类型。 - djabi

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