You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不使用模板合并两个仅成员变量类型不同的类的重复代码?

问题描述

我有两个类: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 07:32:16