c++ - 如何在没有运行时开销的情况下轻松配置类?

标签 c++ templates arduino microcontroller specialization

我最近开始玩 Arduinos,并且来自 Java 世界,我正在努力应对微 Controller 编程的限制。我越来越接近 Arduino 2 KB RAM 的限制。
我经常面临的一个难题是如何在不增加其编译大小的情况下使代码更具可重用性和可重构性,尤其是当它仅用于特定构建中的一个特定配置时。
例如 7-segment number displays 的通用驱动程序类至少需要为每个 LED 段配置 I/O 引脚编号,以使该类可用于不同的电路:

class SevenSeg {
private:
    byte pinA; // top
    byte pinB; // upper right
    byte pinC; // lower right
    byte pinD; // bottom
    byte pinE; // lower left
    byte pinF; // upper left
    byte pinG; // middle
    byte pinDP; // decimal point
    
public:
    void setSegmentPins(byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte dp) {
        /* ... init fields ... */
    }
    
    ...
};

SevenSeg display;
display.setSegmentPins(12, 10, 7, 6, 5, 9, 8, 13);
...
我在这里为灵 active 付出的代价是额外字段的 8 个额外 RAM 字节,以及每次类访问这些字段时更多的代码字节和开销。但是在任何特定电路上对此类进行任何特定编译时,此类仅使用一组值 进行实例化,并且这些值在被读取之前被初始化。它们实际上是恒定的,就好像我写过:
class SevenSeg {
private:
    static const byte pinA = 12;
    static const byte pinB = 10;
    static const byte pinC = 7;
    static const byte pinD = 6;
    static const byte pinE = 5;
    static const byte pinF = 9;
    static const byte pinG = 8;
    static const byte pinDP = 13;
    
    ...
};
不幸的是,GCC 不同意这种理解。
我考虑使用"template":
template <byte pinA, byte pinB, byte pinC, byte pinD, byte pinE, byte pinF, byte pinG, byte pinDP> class SevenSeg {
    ...
};

SevenSeg<12, 10, 7, 6, 5, 9, 8, 13> display;
对于这个简化的示例,其中特定参数是同质的,并且总是指定的,这并不太麻烦。但我需要更多参数:例如,我还需要能够为显示器的数字配置公共(public)引脚的数量(用于可配置的数字数量),并配置 LED 极性:共阳极或共阴极。 future 可能会有更多选择。将它塞进模板初始化行会变得很难看。而且这个问题不仅限于这一类:我到处都陷入这种裂痕。
我想让我的代码可配置、可重用、美观,但是每次我向某些东西添加可配置字段时,它都会消耗更多的 RAM 字节才能恢复到相同级别的功能。
看着空闲内存数量逐渐减少,感觉就像因为编写代码而受到惩罚,这并不好玩。
我觉得我错过了一些技巧。

我为这个问题添加了一个赏金,因为虽然我非常喜欢@alterigel 显示的模板配置结构,但我不喜欢它强制重新指定每个字段的精确类型,这很冗长而且感觉很脆弱。数组尤其令人讨厌(显然,由于一些 Arduino 限制,例如不支持 constexpr inlinestd::array )。
配置结构最终几乎完全由结构样板组成,而不是我想要的:只是键和值的简明描述。
由于不了解 C++,我一定会错过一些替代方案。更多模板?宏?遗产?内联技巧?为了避免这个问题变得过于宽泛,我对具有 的方法特别感兴趣。零运行时开销 .

编辑:我已经从这里删除了其余的示例代码。我把它包括在内是为了避免被“过于宽泛”的警察关闭,但它似乎分散了人们的注意力。我的问题与 7 段显示器无关,甚至与 Arduinos 无关。 我只想知道 C++ 中在编译时配置类行为的方法,这些行为具有零运行时开销。

最佳答案

您可以使用单个 struct将这些常量封装为命名的静态常量,而不是单独的模板参数。然后你可以通过 struct type 作为单个模板参数,模板可以按名称查找每个常量。例如:

struct YesterdaysConfig {
    static const byte pinA = 3;
    static const byte pinB = 4;
    static const byte pinC = 5;
    static const byte pinD = 6;
    static const byte pinE = 7;
    static const byte pinF = 8;
    static const byte pinG = 9;
    static const byte pinDP = 10;
};

struct TodaysConfig {
    static const byte pinA = 12;
    static const byte pinB = 10;
    static const byte pinC = 7;
    static const byte pinD = 6;
    static const byte pinE = 5;
    static const byte pinF = 9;
    static const byte pinG = 8;
    static const byte pinDP = 13;

    // Easy to extend:
    static const byte extraData = 0xFF;
    using customType = double;
};
您的模板可以期望在结构范围内提供所需字段作为命名静态变量的任何类型。
一个示例模板实现:
template<typename ConfigT>
class SevenSeg {
public:
    SevenSeg() {
        theHardware.setSegmentPins(
            ConfigT::pinA,
            ConfigT::pinB,
            ConfigT::pinC,
            ConfigT::pinD,
            ConfigT::pinE,
            ConfigT::pinF,
            ConfigT::pinG,
            ConfigT::pinDP
        );
    }
};
以及一个示例用法:
auto display = SevenSeg<TodaysConfig>{};
Live Example

关于c++ - 如何在没有运行时开销的情况下轻松配置类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66789893/

相关文章:

c++ - 如何不让我的堆栈溢出?

C++ 检查字符串是否是有效的正则表达式字符串,无异常

C++ 应该在 header 和源代码中包含字符串

c++ - 可变参数模板的显式模板实例化

c++ - 对于基于 Arduino Sketch 的照度计, 'loop' 之外的功能不会被触发/触发

ssl - 如何从 Arduino 解决 Firebase 问题?

c++ - 连 catch 体顶点 OpenGL

c++ - 使用 typedef 类型实现函数的 VC++ 类模板

c# - 如果 asp.net 页面中的代码 <% ... %> 在 c# 中获取 RepeaterItem

java - 用Java独立阅读Arduino A0、A1和A2