C++协变返回类型因不完整类型报错问题咨询
首先直接给结论:你的代码不符合C++标准要求,所以编译器报错是合理的;不过我们有简单的调整方法来解决这个问题。
为什么你的代码无法编译?
C++标准对协变返回类型有明确的要求:当你在派生类中重写基类的虚函数时,如果返回类型是指针或引用,那么这个返回类型对应的类必须是完整类型(即已经被完全定义,而不是仅仅前向声明)。
在你的代码里,当编译器处理Odd类中的Even const* increment()const声明时,Even只是一个前向声明——编译器此时只知道Even是一个类,但不知道它是否继承自Number,也没有它的完整类定义信息,这就违反了协变返回类型的要求,所以Clang和VS2015都会报错。
这里要澄清:你提到的常量性一致是对的,但问题根本不在常量性,而在返回类型的类完整性上。
C++标准是否支持这种场景?
严格来说,标准不支持用不完整类作为协变返回类型。协变返回的核心是派生类的返回类型必须是基类返回类型的派生类,而编译器需要在函数声明点确认这一点——不完整类无法提供足够的信息让编译器验证继承关系,所以标准禁止这种写法。
解决方法:调整函数声明与定义的位置
最简单的修复方式是:把increment函数的定义放到两个子类都完全定义之后,类内只保留声明。这样当编译器处理函数定义时,Even和Odd都是完整类型,就能满足协变返回的要求了。
修改后的代码如下:
class Number { public: virtual ~Number () = default; virtual Number const * increment()const = 0; }; // 前向声明两个子类 class Even; class Odd : public Number { public: // 只声明函数,不定义 Even const * increment()const override; }; class Even : public Number { public: // 同样只声明 Odd const * increment()const override; }; // 现在两个类都完整了,定义函数 Even const * Odd::increment()const { // 这里写你的实现逻辑,比如返回一个Even实例 return new Even(); } Odd const * Even::increment()const { // 这里写你的实现逻辑 return new Odd(); }
这样调整后,Clang 11和VS2015都能正常编译,同时完全保留了协变返回类型的特性。
替代实现建议(如果调整顺序有困难)
如果因为某些限制无法调整类的定义顺序,你也可以暂时返回基类Number const*,然后在调用端通过dynamic_cast转换到具体的子类类型。不过这种方式会失去协变返回带来的类型安全优势,需要额外的运行时检查,所以只推荐在特殊场景下使用。
示例代码:
class Number { public: virtual ~Number () = default; virtual Number const * increment()const = 0; }; class Odd : public Number { public: Number const * increment()const override; }; class Even : public Number { public: Number const * increment()const override; }; // 定义函数 Number const * Odd::increment()const { return new Even(); } Number const * Even::increment()const { return new Odd(); } // 调用端 void example() { Odd const* odd = new Odd(); // 转换到Even类型 Even const* even = dynamic_cast<Even const*>(odd->increment()); if (even) { // 处理Even实例 } }
不过还是优先推荐第一种方法,因为它更符合C++的类型安全设计。
内容的提问来源于stack exchange,提问作者Myon

