C++中接口类与实现类存入vector后调用方法出现异常行为
搞定这个C++多态异常的核心思路
嗨,这个问题我之前踩过坑!核心原因其实是C++多态的基础规则没落实到位:只有被声明为virtual的函数,才能通过基类指针/引用触发动态绑定,调用到子类的具体实现。
为什么无参方法能正常工作?
你说无参方法没问题,大概率是因为它在抽象类里被声明成了纯虚函数(比如virtual void func() = 0;)——纯虚函数天然是virtual的,所以通过shared_ptr<Interface>调用时,会自动根据实际指向的子类对象,动态找到对应的实现。
带参方法掉链子的原因
而带参方法出问题,基本逃不开两种情况:
- 抽象类里的带参方法没加
virtual:这时候编译器会把它当成普通的非虚函数,调用时直接根据指针的静态类型(也就是Interface*)来匹配,自然就调用了接口类的实现,完全不会管实际指向的是哪个子类。 - 子类的带参方法签名和基类不匹配:哪怕基类加了
virtual,如果子类的方法参数类型、数量或者const修饰符和基类不一样,又没加override关键字,那子类的方法其实是重载而非重写,同样触发不了多态。
直接上修正方案
第一步:给抽象类的带参方法加上virtual
如果希望子类必须实现这个方法,就设为纯虚;如果需要默认实现,也可以留着函数体:
class Interface { public: virtual void func1() = 0; // 无参纯虚,正常工作 virtual void func2(int x) { // 带参方法加virtual! std::cout << "Interface::func2 (默认实现)" << std::endl; } virtual ~Interface() = default; // 别忘了虚析构!不然shared_ptr管理子类会漏内存 };
第二步:用override关键字确保重写正确
override是C++11以后的神技,它会让编译器帮你检查子类方法的签名是否和基类的虚函数完全一致。如果签名不对,编译器直接报错,避免你踩重载的坑:
class ConcreteA : public Interface { public: void func1() override { std::cout << "ConcreteA::func1" << std::endl; } void func2(int x) override { // 加override,编译器帮你把关 std::cout << "ConcreteA::func2: " << x << std::endl; } };
修正后的完整可运行代码
#include <iostream> #include <vector> #include <memory> class Interface { public: virtual void func1() = 0; virtual void func2(int x) { std::cout << "Interface::func2 (默认实现)" << std::endl; } virtual ~Interface() = default; }; class ConcreteA : public Interface { public: void func1() override { std::cout << "ConcreteA::func1" << std::endl; } void func2(int x) override { std::cout << "ConcreteA::func2: " << x << std::endl; } }; class ConcreteB : public Interface { public: void func1() override { std::cout << "ConcreteB::func1" << std::endl; } void func2(int x) override { std::cout << "ConcreteB::func2: " << x << std::endl; } }; int main() { std::vector<std::shared_ptr<Interface>> vec; vec.push_back(std::make_shared<ConcreteA>()); vec.push_back(std::make_shared<ConcreteB>()); vec[0]->func1(); // 输出:ConcreteA::func1 vec[0]->func2(10); // 输出:ConcreteA::func2: 10 vec[1]->func1(); // 输出:ConcreteB::func1 vec[1]->func2(20); // 输出:ConcreteB::func2: 20 return 0; }
额外提醒
- 基类必须加虚析构函数:用
shared_ptr<Interface>持有子类对象时,如果基类没有虚析构,子类的析构函数不会被调用,会造成内存泄漏。 - 永远不要省略
override:哪怕你觉得签名绝对正确,这个关键字能帮你在编译期就发现问题,比运行时调试省太多时间。
内容的提问来源于stack exchange,提问作者AlesG
相关产品推荐
相关产品推荐

