记一个bug : C2555 overriding virtual function return type differs and is not covariant from

本文通过一个实例讲述了在C++编程中遇到的编译错误C2555,即虚函数返回类型不匹配的问题。错误源于一个前置声明的类名不在预期的命名空间内,导致基类和派生类的虚函数返回类型不同。解决方案是修正类名的命名空间或者直接包含头文件。作者强调了理解编译器错误信息的重要性,并提醒开发者不应忽视编译错误。
摘要由CSDN通过智能技术生成

今天同事遇到一个编译错误找我帮忙看一下。原以为编译错误应该好解决,没想到搞了半天才fix,在此记录一下。
由于公司代码涉及机密且比较复杂,不方便贴出来,因此我写了一个简化的例子来展示这个编译错误是如何产生的。

1.代码

假设我们有一个namespace名为zoo, 在zoo中有一个animal基类,一个name类,还有一个dog类继承自animal类。这三个类分别位于三个头文件。代码如下:

//animal.h
#pragma once
class Name;
namespace zoo {
    class Animal {
        virtual Name* GetName() const = 0;
    };
}

//name.h
#pragma once
namespace zoo {
    class Name {

    };
}

//dog.h
#pragma once
#include "animal.h"
#include "name.h"
namespace zoo {
    class Dog :public  Animal{
    public:
        virtual Name* GetName() const override { return name; }
    private: 
        Name* name;
    };
}
// main.cpp
#include "dog.h"
int main()
{
    return 0;
}

2. 原因分析

这段代码很简单,不需要解释。用msvc编译器来编译会得到下面的编译错误:

Error    C2555    'zoo::Dog::GetName': overriding virtual function return type differs and is not covariant from 'zoo::Animal::GetName'    Project2    C:\Users\vincent.zheng\source\repos\Project2\Project2\dog.h    7

第一次看这个报错信息真是看的我一脸懵,一般来讲,C2555报错是由于派生类重写基类的虚函数,但是返回类型不一致导致的,根据微软的官方文档

'class1::function1': overriding virtual function return type differs and is not covariant from 'class2::function2'

A virtual function and a derived overriding function have identical parameter lists but different return types.

可是在我们这个例子中,基类和派生类的GetName()函数返回值明明是一样的,都是Name类型。这是一个实实在在的类型,根本不存在covariant的问题。那么问题到底出在哪里呢?

一开始我认为编译器报错信息不准确,就忽略了这个报错,在代码别的地方一通瞎改。可想而知,这样搞肯定是不管用的,搞了一个多小时,这个报错始终跟个牛皮癣一样英魂不散。后来实在累了,于是起来活动一下,喝杯水,水水群再重新坐下来仔细看代码。

终于让我给看出点端倪来,我发现在animal.h文件中,用了一个class前置声明,是这样写的(当然具体的项目代码分散在各个文件,而且位置距离很远,没有这么简单):

class Name;
namespace zoo {
    class Animal {
        virtual Name* GetName() const = 0;
    };
}

这简直是个大坑!class Name的前置声明放在了namespace zoo外面,也就是说编译在编译基类的GetName函数时会认为这里Name是一个全局的class,而不是在namespace zoo里面定义的那个Name。而在文件dog.h中包含了name.h头文件,因此dog类的getName函数返回值是zoo::Name类型。基类和派生类的函数签名分别为

Name* zoo::Animal::GetName()const;
zoo::Name zoo::Dog::GetName() const; 

显然这两者的返回值是不一样的,因此会报C2555错误:

Error    C2555    'zoo::Dog::GetName': overriding virtual function return type differs and is not covariant from 'zoo::Animal::GetName'    Project2    C:\Users\vincent.zheng\source\repos\Project2\Project2\dog.h    7

3. 解决方法

知道了原因之后,解决办法自然而然也就有了。要么我们animal.h中的前置声明写在namespace zoo里面,如:

#pragma once
namespace zoo {
class Name;
	class Animal {
		virtual Name* GetName() const = 0;
	};
}

要么,我们不用前置声明,直接include 头文件

#pragma once
#include "Name.h"
namespace zoo {
	class Animal {
		virtual Name* GetName() const = 0;
	};
}

4. 总结

通过这个例子来看编译器的报错还是相当靠谱的,可惜的是这个报错信息只把函数名打印出来了,并没有打印函数签名。如果把函数签名也打印出来,我想这个错误应该分分钟就可以被解决掉。对比下g++编译器的报错信息,明显比MSVC提供的信息要多一些。

In file included from main.cpp:1:
./dog.h:7:17: error: return type of virtual function 'GetName' is not covariant with the return type of the function it overrides ('zoo::Name *' is not derived from 'Name *')
                virtual Name* GetName() const override { return name; }
                        ~~~~~ ^
./animal.h:5:17: note: overridden virtual function is here
                virtual Name* GetName() const = 0;
                        ~~~~~ ^
1 error generated.

当然,说来说去还是自己经验不足,技术差不能怪编译器:)。这个问题也给了我一个教训:不要轻易放过编译器的报错,大多数情况下编译器还是比我们自己靠谱的多!

Polymorphism is a fundamental concept in object-oriented programming that allows objects of different classes to be treated as if they were objects of the same class. In Java, there are two common ways to implement polymorphism: Overloading and Overriding. 1. Overloading: This occurs when multiple methods have the same name but different parameters. The method that is called depends on the type and number of arguments passed to it. Overloading is a way to provide different implementations of the same method for different types of input. Example: ``` public class MathOperations { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } } ``` In the example above, there are two methods called `add`, but one takes two `int` parameters, and the other takes two `double` parameters. When the `add` method is called, the appropriate method is selected based on the parameters passed to it. 2. Overriding: This occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The subclass method must have the same name, return type, and parameters as the superclass method. The subclass method can also have a more specific access modifier than the superclass method. Example: ``` public class Shape { public void draw() { System.out.println("Drawing a shape."); } } public class Circle extends Shape { @Override public void draw() { System.out.println("Drawing a circle."); } } ``` In the example above, the `Circle` class overrides the `draw` method of its superclass `Shape`. When the `draw` method is called on a `Circle` object, the `Circle` implementation of the method is executed instead of the `Shape` implementation.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值