纯虚函数与最佳实践:Handler类依赖抽象类的实现疑问
补充说明
正确的引用成员定义方式为Accelerometer &acc,正确的引用初始化方式为Handler(Accelerometer &a): acc(a){}。
问题场景:
Handler类依赖成员acc的void doThing()函数。Handler接收外部实例化的acc的引用,只要void doThing()已实现,Handler不关心acc的具体类型。
我希望确保doThing()被实现,同时能使用基类。目前我通过使用基类Accelerometer的指针实现,避免了虚表缺失和未实现的问题,但如果传递AccSensor的引用而非指针则无法运行。现提出两个问题:
- 为何会出现这种差异?我推测是编译器在编译时知道指针指向的内容不一定已定义,因此不会尝试关联方法,从而绕过纯虚函数实例化检查,但不确定该理解是否正确,希望得到深入解释。
- 我的实现是否属于最佳实践?如果不是,有没有合适的替代方案?
可编译运行的代码
class Accelerometer { public: virtual void doThing()=0; };
class AccSensor: public Accelerometer { void doThing(){std::cout << "doThing\n"; } };
class Handler { private: Accelerometer* acc; public: Handler(Accelerometer *a){ acc = a; } void someFunc(){ acc->doThing(); } };
int main() { AccSensor acc; Handler h(&acc); h.someFunc(); return 0; }
无法编译的代码
class Handler { private: Accelerometer acc; public: Handler(Accelerometer &a){ acc = a; } void someFunc(){ acc.doThing(); } };
int main() { AccSensor acc; Handler h(&acc); h.someFunc(); return 0; }
原因很明显:无法实例化纯虚类。尝试将纯虚函数改为普通虚函数:
class Accelerometer { public: virtual void doThing(); };
但会因void doThing()未定义导致虚表缺失和未定义引用错误。虽然可以提供默认实现并抛出异常,但这显得繁琐。因此我选择了指针实现方式,不确定这是否属于最佳实践。
问题解答
1. 指针与直接实例的差异原因
核心差异源于纯虚类的实例化规则和多态实现机制:
- 当
Handler中定义Accelerometer acc成员时,编译器必须为该成员分配内存并构造对象。但Accelerometer是含纯虚函数的抽象类,C++明确禁止实例化抽象类——纯虚函数无实现,这类对象无法调用对应函数,编译器直接报错阻止无效操作。 - 使用指针
Accelerometer* acc时,并没有实例化抽象类本身:指针只是存储地址的变量,它指向的是外部已实例化的AccSensor子类对象(已实现纯虚函数)。编译Handler时,编译器只需确认指针类型合法,无需知道指针具体指向的对象类型;多态调用(acc->doThing())是运行时通过虚表动态查找实现的,只要运行时指针指向合法子类实例,就能正确调用对应函数。
你的推测方向正确,但更准确的表述是:指针本身不涉及抽象类实例化,仅持有地址;而直接定义抽象类成员必须实例化抽象类,这是C++语法不允许的。
2. 实现是否为最佳实践?替代方案
你的指针实现可行,但并非最安全的最佳实践——裸指针存在悬空风险(若外部AccSensor对象提前销毁,Handler中的指针会变成野指针)。更推荐两种替代方案:
方案一:使用引用成员(正确写法)
按照补充说明的规范,将Handler成员改为引用,并用初始化列表初始化:
class Handler { private: Accelerometer &acc; // 引用成员 public: // 必须用初始化列表初始化引用 Handler(Accelerometer &a): acc(a){} void someFunc(){ acc.doThing(); } }; // main函数调用 int main() { AccSensor acc; Handler h(acc); // 直接传引用,无需取地址 h.someFunc(); return 0; }
引用和指针一样支持多态,且引用一旦初始化就不能改变指向,避免了指针空悬或误改指向的问题,比裸指针更安全。注意:引用成员必须在构造函数初始化列表中初始化,不能在构造函数体内赋值。
方案二:使用智能指针(适合动态分配对象场景)
如果AccSensor是动态分配的(比如用new创建),推荐用std::shared_ptr或std::unique_ptr管理生命周期,彻底避免悬空指针:
#include <memory> class Handler { private: std::shared_ptr<Accelerometer> acc; public: Handler(std::shared_ptr<Accelerometer> a): acc(std::move(a)){} void someFunc(){ acc->doThing(); } }; int main() { auto acc = std::make_shared<AccSensor>(); Handler h(acc); h.someFunc(); return 0; }
智能指针会自动管理对象生命周期,当最后一个持有该指针的对象销毁时,AccSensor会被自动释放,完全杜绝野指针问题。
总结
- 若
AccSensor是栈上创建的局部对象,优先用引用成员实现,安全简洁; - 若为堆上动态分配的对象,用智能指针更稳妥;
- 裸指针虽可行,但存在安全隐患,不推荐在现代C++中使用。
内容的提问来源于stack exchange,提问作者Christian K

