如何不使用模板合并两个仅成员变量类型不同的类的重复代码?
我有两个类:Motor_Distance和Motor_Displacement,二者代码几乎完全一致,仅成员变量count的类型以及访问器convToCount的返回类型存在差异:
Motor_Displacement中count是long类型,支持正负值;Motor_Distance中count是unsigned long类型,仅存正值以获得更大的存储范围。
原始重复代码如下:
constexpr double pi = 3.14159; class Motor_Distance{ private: unsigned long count; float wheel_diameter; unsigned int pulses_per_revolution; public: unsigned long convToCount(){ return count; } float convToMeters(){ return convToCount() * pi * wheel_diameter / pulses_per_revolution; } }; class Motor_Displacement{ long count; float wheel_diameter; unsigned int pulses_per_revolution; public: long convToCount(){ return count; } float convToMeters(){ return convToCount() * pi * wheel_diameter / pulses_per_revolution; } }; int main(){ }
我已经通过模板基类实现了代码复用:
constexpr double pi = 3.14159; template<typename Travel_Type> class Motor_Travel{ Travel_Type count; float wheel_diameter; unsigned int pulses_per_revolution; public: Travel_Type convToCount(){ return count; } float convToMeters(){ return convToCount() * pi * wheel_diameter / pulses_per_revolution; } }; class Motor_Distance : public Motor_Travel<unsigned long>{}; class Motor_Displacement : public Motor_Travel<long>{}; int main(){ }
请问是否存在不使用模板的方法来消除这些重复代码?
确实有几种不依赖模板的方式可以消除重复代码,但各有优缺点:
1. 提取通用基类,派生类处理类型差异
把两个类中通用的成员(wheel_diameter、pulses_per_revolution以及convToMeters的核心计算逻辑)放到基类中,派生类只负责维护自身特有的count类型和对应的访问方法:
constexpr double pi = 3.14159; class Motor_Base { protected: float wheel_diameter; unsigned int pulses_per_revolution; public: // 为两种count类型提供重载的计算方法 float calcMeters(long count) { return count * pi * wheel_diameter / pulses_per_revolution; } float calcMeters(unsigned long count) { return count * pi * wheel_diameter / pulses_per_revolution; } }; class Motor_Distance : public Motor_Base { private: unsigned long count; public: unsigned long convToCount() { return count; } float convToMeters() { return calcMeters(count); } }; class Motor_Displacement : public Motor_Base { private: long count; public: long convToCount() { return count; } float convToMeters() { return calcMeters(count); } };
这种方式将通用逻辑收拢到基类,派生类仅需维护自身类型相关的少量代码,避免了大部分重复。缺点是需要手动为两种类型提供重载方法,本质是手动处理类型适配。
2. 使用组合而非继承
定义一个封装通用计算逻辑的工具类,让Motor_Distance和Motor_Displacement持有该工具类的实例,各自维护自己的count成员:
constexpr double pi = 3.14159; class Motor_Calculator { private: float wheel_diameter; unsigned int pulses_per_revolution; public: Motor_Calculator(float diameter, unsigned int pulses) : wheel_diameter(diameter), pulses_per_revolution(pulses) {} float toMeters(long count) { return count * pi * wheel_diameter / pulses_per_revolution; } float toMeters(unsigned long count) { return count * pi * wheel_diameter / pulses_per_revolution; } }; class Motor_Distance { private: unsigned long count; Motor_Calculator calc; public: Motor_Distance(float diameter, unsigned int pulses) : calc(diameter, pulses) {} unsigned long convToCount() { return count; } float convToMeters() { return calc.toMeters(count); } }; class Motor_Displacement { private: long count; Motor_Calculator calc; public: Motor_Displacement(float diameter, unsigned int pulses) : calc(diameter, pulses) {} long convToCount() { return count; } float convToMeters() { return calc.toMeters(count); } };
组合方式比继承更灵活,避免了基类保护成员的暴露问题,但派生类仍需编写少量重复的构造和转发代码。
3. 类型擦除(不推荐)
可以用void*或C++17的std::any存储count,运行时再强制转换类型。但这种方式会丢失编译期类型安全,容易引发运行时错误,维护成本极高,仅作思路参考:
#include <any> constexpr double pi = 3.14159; class Motor_Generic { private: std::any count; float wheel_diameter; unsigned int pulses_per_revolution; public: Motor_Generic(unsigned long cnt, float diameter, unsigned int pulses) : count(cnt), wheel_diameter(diameter), pulses_per_revolution(pulses) {} Motor_Generic(long cnt, float diameter, unsigned int pulses) : count(cnt), wheel_diameter(diameter), pulses_per_revolution(pulses) {} unsigned long getDistanceCount() { return std::any_cast<unsigned long>(count); } long getDisplacementCount() { return std::any_cast<long>(count); } float convToMeters() { if (count.type() == typeid(unsigned long)) { return getDistanceCount() * pi * wheel_diameter / pulses_per_revolution; } else if (count.type() == typeid(long)) { return getDisplacementCount() * pi * wheel_diameter / pulses_per_revolution; } return 0.0f; } };
总结
相比之下,提取通用基类的方案是最实用的非模板实现,既能有效消除重复代码,又保留了类型安全和可读性。但需要明确:模板是C++中处理这种"仅类型差异"场景最优雅的方案,非模板方案本质都是手动模拟模板的类型适配逻辑,代码量和维护成本会更高。
内容的提问来源于stack exchange,提问作者timmy george

