【问题标题】:Splitting hardware dependant class in C++ [closed]在 C++ 中拆分硬件相关类 [关闭]
【发布时间】:2019-08-05 21:38:55
【问题描述】:

我想就在 C++ 中拆分硬件相关代码的最佳方式询问您的意见。 目前我有一个类正在实现一个使用#ifdef 的温度传感器接口:

class TemperatureSensor : public TemperatureI
{
TemperatureSensor()
{
#ifdef PLATFORM_A
/* Platform A dependant code */
#elsif PLATFORM_B
/* Platform B dependant code */
#endif
}

~TemperatureSensor()
{
#ifdef PLATFORM_A
/* Platform A dependant code */
#elsif PLATFORM_B
/* Platform B dependant code */
#endif
}

int getTemp()
{
#ifdef PLATFORM_A
/* Platform A dependant code */
#elsif PLATFORM_B
/* Platform B dependant code */
#endif
}
};

当然有几种方法和不止 1 行硬件相关代码。

现在我想要的是:

  1. 从代码中一起删除 ifdefs,或者如果不可能,将其限制在一个地方
  2. 决定创建正确的实现以编译时间(使用 CMakeLists.txt)
  3. 尽可能少的样板代码(可能有一个帮助类,因为我打算在多个地方使用此解决方案,但每个地方都应有最低开销)

一种解决方案是使用 PIMPL 和工厂,但不幸的是,这会导致文件过多和大量间接。

  • TemperatureSensor.cpp
  • TemperatureSensor.hpp
  • TemperatureSensorPlatformFactory.hpp
  • TemperatureSensorPlatformA.cpp
  • TemperatureSensorPlatformA.hpp
  • TemperatureSensorPlatformAFactory.cpp
  • TemperatureSensorPlatformB.cpp
  • TemperatureSensorPlatformB.hpp
  • TemperatureSensorPlatformBFactory.cpp

还有如下代码:

class TemperatureSensor : public TemperatureI
{
TemperatureSensor() :
pImpl(TemperatureSensorFactory.create())
{}

int getTemp()
{
return pImpl.getTemp();
}
};

你有更好的想法吗?

【问题讨论】:

  • 您可以有条件地编译 整个 TemperatureSensor 类(可能使用不同的名称以避免混淆,并允许在一个程序中使用两个传感器,并使用 typedef 将 TemperatureSensor 映射到正确的类)。不同传感器类型之间共享多少代码共性?
  • 当然,最好的方法取决于你的整体设计和意图的性质,这不是很清楚。术语“硬件相关”代码是否与支持不同温度传感器或使用相同温度传感器的不同平台有关?

标签: c++ embedded factory


【解决方案1】:

一种方法是将特定于平台的代码放在单独的实现文件中:

  • TemperatureSensorPlatformA.cpp
  • TemperatureSensorPlatformA.hpp
  • TemperatureSensorPlatformB.cpp
  • TemperatureSensorPlatformB.hpp

然后在 TemperatureSensor.hpp 中你有:

#ifdef PLATFORM_A
    #include "TemperatureSensorPlatformA.hpp"
    using TemperatureSensor = TemperatureSensorPlatformA;

#elsif PLATFORM_B
    #include "TemperatureSensorPlatformB.hpp"
    using TemperatureSensor = TemperatureSensorPlatformB;
#endif

这会将ifdef's 保留在一个位置并移除大部分样板。当然,由于其他原因,pimpl 仍然很有用——比如隐藏实现细节。

【讨论】:

    【解决方案2】:

    除非进一步澄清,否则有两种方法可以解释“硬件相关”一词的使用,这将为所需的设计选择提供依据:

    相同的传感器,不同的平台

    目标:鉴于此解释,目标是确保温度传感器驱动程序代码(即执行与传感器通信和解释来自传感器的数据的软件)设计在时尚,以便它可以跨不同的微控制器(“MCU”)供应商和架构运行。

    设计:解决此问题的最佳方法是确定传感器驱动程序代码的哪些方面因 MCU 而异?我只想到两件事:

    1. 本机数据类型(即字 = 8 位/16 位/32 位?)
    2. 传输层(即 SPI 或 I2C 外设架构和驱动程序)

    通过确保始终使用固定宽度的数据基元类型(例如,uint32_t 而不是 unsigned int),可以轻松解决第 1 点。第 2 点可以通过多种方式解决,包括问题中描述的条件编译和 mortenvp 的答案。

    但是,最简洁和最灵活的方法是通过基于策略的设计,因为它使我们能够将所有特定于平台的传输逻辑提取到一个单独的类中,该类作为模板参数注入到传感器驱动程序类中。现在要支持一个新平台,您只需要实现特定于平台的传输策略类,保持传感器驱动程序业务逻辑不变(即没有(重新)引入错误)。

    特定于平台的传输策略:

    /* In stm32_spi_transport.hpp */
    class STM32_SPI {
        uint32_t read() { /* STM32-specific SPI read logic */ }
    };
    
    /* In stm32_i2c_transport.hpp */
    class STM32_I2C {
        uint32_t read() { /* STM32-specific I2C read logic */ }
    };
    
    /* In pic_spi_transport.hpp */
    class PIC_SPI {
        uint32_t read() { /* PIC-specific SPI read logic */ }
    };
    

    不变的传感器驱动程序和应用程序

    /* In temp_sensor.hpp */
    template<typename TTransport>
    class TempSensor {
    private:
        TTransport& transport_;
    public:
        TempSensor(const TTransport& t) : transport_{t} { }
    
        float getTemp() { return convert(transport_.read()); }
    
    private:
        float convert(uint32_t raw_val) {
            return raw_val * 2.3; /* for simplicity */
        }
    };
    
    /* In main.cpp */
    int main()
    {
        STM32_SPI transport{}; /* If STM32 SPI */
        /* or */
        STM32_I2C transport{}; /* If STM32 I2C */
        /* or */
        PIC_SPI   transport{}; /* Or if PIC */
    
        /* Stays constant */
        TempSensor sensor{transport};
        float temp = sensor.getTemp();
    
        return 0;
    }
    

    相同的平台,不同的传感器

    目标:鉴于此解释,目标是确保温度传感器驱动程序代码可以支持不同版本的传感器。

    设计:这需要进一步澄清,因为有两种方法可以做到这一点:

    1. 完全不相关的传感器(例如 Analog Devices 的模拟热敏电阻或 TI 的数字热电偶)。
    2. 同一传感器的不同版本(例如 8 位或 10 位读数)

    真正解决第 1 点的最佳方法是确保驱动程序类公开相同的接口,以便应用程序代码可以以相同的方式处理它们(例如,像 sensor = AnalogThermistor();sensor = SPIThermocouple(); 一样创建每个,但与两者交互比如val = sensor.getTemp();)。然而,第 2 点最好通过某种形式的编译时条件来解决。

    条件编译只会在处理传感器变体中的硬件差异的少数地方进行 - 例如在convert() 中,您将原始位转换为浮点温度值。如果您可以访问 C++17,那么if constexpr() 是实现这一目标的最佳方式,否则我建议预处理器#ifdefs。但同样,在这种情况下,它们将被巧妙地定位和限制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多