如何定义接收派生类为参数的虚函数?子类重写问题求解
解决子类重写纯虚函数时参数不匹配的问题
你遇到的这个错误核心原因很明确:C++中重写虚函数要求函数签名完全一致,包括参数类型。父类Obj的cmp参数是const Obj&,但子类MyString把参数改成了const MyString&——这不是重写,而是重载了一个新的函数!编译器自然会认为父类的纯虚函数没有被实现,所以抛出那个错误。
要让每个子类既能合法重写父类的虚函数,又能访问自己的私有成员,这里有两种实用的解决方案:
方案一:用dynamic_cast做类型转换(最直接)
严格按照父类的签名重写cmp函数,然后在函数内部把传入的const Obj&转换成当前子类的类型,转换成功后就能访问私有成员了。记得加上override关键字,让编译器帮你检查是否真的重写了父类的虚函数(避免拼写或参数错误)。
示例代码:
#include <string> #include <stdexcept> class Obj { public: virtual int cmp(const Obj& val) = 0; virtual ~Obj() = default; // 父类必须有虚析构函数! }; class MyString : public Obj { private: std::string data; public: // 严格匹配父类签名,用override确保重写 int cmp(const Obj& val) override { // 尝试将Obj&转换为MyString& const MyString* other = dynamic_cast<const MyString*>(&val); if (!other) { // 处理类型不匹配的情况:比如返回特殊值、抛出异常,或者按自定义规则排序 // 这里示例抛出异常,你可以根据业务需求调整 throw std::invalid_argument("Cannot compare MyString with non-MyString type"); } // 转换成功,直接访问私有成员做比较 return this->data.compare(other->data); } // 构造函数,方便初始化 MyString(std::string s) : data(std::move(s)) {} }; // 另一个子类示例:MyInt class MyInt : public Obj { private: int value; public: int cmp(const Obj& val) override { const MyInt* other = dynamic_cast<const MyInt*>(&val); if (!other) { return -1; // 自定义规则:MyInt比其他类型"小" } if (this->value > other->value) return 1; else if (this->value < other->value) return -1; else return 0; } MyInt(int v) : value(v) {} };
注意:dynamic_cast需要编译器开启RTTI(运行时类型信息),大多数编译器默认是开启的,如果你的项目关闭了RTTI,需要重新开启。
方案二:双重分发(适合复杂类型比较场景)
如果你的子类之间需要支持更灵活的跨类型比较,比如MyString和MyInt之间也要能比较,可以用双重分发(基于访问者模式)。这种方式代码量稍大,但扩展性更好。
大致思路:
- 在父类
Obj中定义一个接受访问者的纯虚函数accept。 - 定义一个访问者基类,包含针对每个子类的重载函数。
- 每个子类实现
accept函数,调用访问者对应的重载方法,这样就能在访问者中处理不同类型的比较逻辑。
示例简化版:
class ObjVisitor; // 前向声明 class Obj { public: virtual int cmp(const Obj& val) = 0; virtual void accept(ObjVisitor& visitor) const = 0; virtual ~Obj() = default; }; // 访问者基类,定义所有子类的比较接口 class ObjVisitor { public: virtual void visit(const MyString& str) = 0; virtual void visit(const MyInt& num) = 0; int result; // 存储比较结果 }; // MyString类 class MyString : public Obj { private: std::string data; public: void accept(ObjVisitor& visitor) const override { visitor.visit(*this); } int cmp(const Obj& val) override { class CompareVisitor : public ObjVisitor { public: const MyString* self; CompareVisitor(const MyString* s) : self(s) {} void visit(const MyString& other) override { result = self->data.compare(other.data); } void visit(const MyInt& other) override { // 自定义MyString和MyInt的比较规则,比如按长度和数值比 result = self->data.size() > other.getValue() ? 1 : -1; } }; CompareVisitor visitor(this); val.accept(visitor); return visitor.result; } std::string getData() const { return data; } MyString(std::string s) : data(std::move(s)) {} }; // MyInt类 class MyInt : public Obj { private: int value; public: void accept(ObjVisitor& visitor) const override { visitor.visit(*this); } int cmp(const Obj& val) override { class CompareVisitor : public ObjVisitor { public: const MyInt* self; CompareVisitor(const MyInt* i) : self(i) {} void visit(const MyString& other) override { result = self->value > other.getData().size() ? 1 : -1; } void visit(const MyInt& other) override { result = (self->value > other.value) ? 1 : (self->value < other.value) ? -1 : 0; } }; CompareVisitor visitor(this); val.accept(visitor); return visitor.result; } int getValue() const { return value; } MyInt(int v) : value(v) {} };
这个方案的好处是,新增子类时只需要在访问者基类中添加对应的visit方法,不用修改已有代码,符合开闭原则。
关键注意点
- 父类必须定义虚析构函数,否则子类对象通过父类指针销毁时会有内存泄漏。
- 永远用
override关键字标记重写的虚函数,编译器会帮你检查签名是否正确,避免低级错误。 - 跨类型比较的逻辑需要你自己定义,比如返回固定值、抛出异常,或者根据业务规则做实际比较,取决于你的需求。
内容的提问来源于stack exchange,提问作者RomaFUN
相关产品推荐
相关产品推荐

