首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Arduino库中C++类存储空间的优化

Arduino库中C++类存储空间的优化
EN

Stack Overflow用户
提问于 2021-01-26 08:44:40
回答 1查看 152关注 0票数 1

我正在编写一个Arduino库来包装引脚函数(digitalReaddigitalWriteanalogRead等)。例如,我有一个RegularPin类,它是一个直通类,一个InvertedPin类,它反转引脚逻辑。这是非常有用的,当从带LED的面包车到继电器板,它反转电路逻辑。我只需要换个班。我还有一个用于按钮的DebouncedPin类,它检查用户按下或释放按钮的时间足够长,以便真正按下/释放按钮。

例如模拟引脚:

代码语言:javascript
复制
// AnalogInPin ------------------------------
class AnalogInPin
{
  public:
    virtual int read()=0;
    virtual int getNo()=0;
};

// AnalogRegInPin ---------------------------

template<int pinNo>
class AnalogRegInPin : public AnalogInPin
{
  public:
    AnalogRegInPin();
    int read();
    int getNo(){return pinNo;}
};

template<int pinNo>
int AnalogRegInPin<pinNo>::read()
{
    return analogRead(pinNo);
}

template<int pinNo>
AnalogRegInPin<pinNo>::AnalogRegInPin()
{
    pinMode(pinNo, INPUT);
}

正如您所看到的,我将引号放入模板声明中,因为它在运行时不会更改,并且我不希望在分配引脚对象时使用该引号,就像在vanilla arduino C代码中一样。我知道课程不可能是零的,但要继续读。接下来,我想编写一个"AveragedPin“类,它将自动多次读取所选的引脚,并且我想将模板化的类堆叠如下:

代码语言:javascript
复制
AveragedPin<cAnalogRegInPin<A0>, UPDATE_ON_READ|RESET_ON_READ> ava0;

甚至:

代码语言:javascript
复制
RangeCorrectedPin<AveragedPin<cAnalogRegInPin<A0>, 
    UPDATE_ON_READ|RESET_ON_READ,RAW_MIN,RAW_MAX,TARGET_RANGE> rcava0;

目前,我将嵌套引脚声明为私有成员,因为它不允许在模板声明中使用类对象。但是,每一层嵌套都毫无用处地吃掉堆栈上的几个字节。

我知道我可以在模板声明中使用引用,但我不太明白它是如何工作/应该如何使用的。我的问题看起来像是空成员优化,但在这里似乎不适用。

我觉得这更像是一个C++问题,而不是一个arduino问题,我不是C++专家。我想这涉及到C++的更高级部分。也许我想要的是不可能的,或者只是最近的C++ (20?)修订。

下面是FixedRangeCorrectedPin类的代码。

代码语言:javascript
复制
template <class P, int rawMin, int rawMax, int targetRange>
class FixedRangeCorrectedPin : public AnalogInPin
{
  public:
    int read();
    int getNo(){return pin.getNo();}
  private:
    P pin;
};

template <class P, int rawMin, int rawMax, int targetRange>
int FixedRangeCorrectedPin<P, rawMin, rawMax, targetRange>::read()
{
    int rawRange = rawMax - rawMin;
    long int result = pin.read() - rawMin;
    if (result < 0) result = 0;
    result = result * targetRange / rawRange;
    if (result > targetRange) result = targetRange;
    return result;
}

我的问题是,我想删除'P引脚‘类成员,并在模板声明中替换它,就像在template <AnalogInPin pin,int rawMin,int rawMax,int targetRange>中那样,因为这里涉及的是哪个引脚在编译时是完全已知的。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-01-26 09:43:47

正如您所看到的,我将引号放入模板声明中,因为它在运行时是不会更改的,并且我不希望在分配pin对象时使用这个引号,就像在vanilla代码中一样。

好的,如果引脚号是一个编译时间常数,它通常是对Arduino的,这个位很好。

但是,使AnalogInPin基类抽象(即添加virtual方法)在实践中将使用至少与您节省的每个对象相同的空间,因为不将引脚存储为整数。

细节是特定于实现的,但是运行时多态需要某种方法来计算,对于AnalogInPin*指向的给定派生类对象,要调用哪个版本的虚拟方法,并且需要存储在每个派生类型的对象中。(您可以验证这是正确的,只需检查sizeof(AnalogInPin),并与sizeof进行比较,这是一个没有virtual方法的完全相同的类。

我知道类不可能大小为零但是..。

对于没有数据成员的基类,有一种特例允许它们不具有额外的大小(派生最多的类型的实例必须至少占用一个字节)。它被称为空基类优化。

暂时将嵌套引脚声明为私有成员,因为它不允许在模板声明中使用类对象。但是,每一层嵌套都毫无用处地吃掉堆栈上的几个字节。

我们可以夷平整个系统(理想情况下,也可以删除抽象基,除非您有需要它的非模板代码):

代码语言:javascript
复制
template <int PIN, template <int> class BASE>
struct AveragedPin: public BASE<PIN>
{
    int read() override { /* call BASE<PIN>::read() several times */ }
    int getNo() override { return PIN; }
};

但是,请注意,我们可以只使用继承的getNo,然后根本不使用PIN。因此,与其将平均引脚实例声明为AveragedPin<MY_PIN, AnalogInPin> myAveragedPin;,不如将定义更改为

代码语言:javascript
复制
template <class BASE>
struct AveragedPin: public BASE
{
    int read() override { /* call BASE::read() several times */ }
    using BASE::getNo; // not really required unless it is hidden
};

并将实例声明为AveragedPin<AnalogInPin<MY_PIN>> myAveragedPin;

范围校正的引脚可以是相似的,但额外的模板参数的标志和最小/最大界限,如果他们是已知的编译时。

类似地,添加到您的问题中的FixedRangeCorrectPin不需要从AnalogInPin派生,然后存储不同的引脚类型。实际上,它可以继承基类。

代码语言:javascript
复制
template <class P,int rawMin,int rawMax,int targetRange>
struct FixedRangeCorrectedPin : public P
{
    int read(); // calls P::read()
    // inherit getNo again
};

再次声明类似于FixedRangeCorrectPin<AnalogInPin<MY_PIN>, RMIN, RMAX, TARGET> myFixedPin;的实例

假设我们将方法更改为static,则在不需要存储开销的情况下,通过可变引脚数的平均值编辑示例

代码语言:javascript
复制
template <class... PINS>
struct AveragedPins
{
  static int read()
  {
    return (PINS::read() + ...) / sizeof...(PINS);
  }
};

这不关心参数是什么类型的引脚,只要它有一个静态的read方法。您可以随意堆叠它:

代码语言:javascript
复制
using a1 = FixedRangeCorrectedPin<A_1, 0, 255, 128>;
using a2 = AnalogInPin<A_2>;
using a3 = AnalogInPin<A_3>;
using a4 = AnalogInPin<A_4>;
using a34 = AveragedPins<a3, a4>;
using all = AveragedPins<a1, a2, a34>;

// now a34::read() = (a3::read() + a4::read())/2
// and all::read() = (a1::read() + a2::read() + a34::read())/3

请注意,所有这些只是类型定义:我们甚至没有为任何对象分配一个字节。

另一个注意事项:我注意到我正在以两种稍微不同的方式使用相同的CLASS::method()语法。

  1. 在上面使用继承的第一个示例中,BASE::read()是一个非虚拟化实例方法调用。

也就是说,我们在BASE对象上调用read方法的版本,您也可以编写this->BASE::read()

它是非虚拟化的,因为虽然基类方法是virtual,但我们知道编译时调用的是正确的覆盖,所以不需要虚拟分派。

在最后的示例中,我们停止了使用继承并将方法变为静态的

  1. PIN::read()没有this,也根本没有对象。

这在原则上最类似于调用一个免费的C函数,尽管我们让编译器为每个不同的PIN值生成一个新的实例(然后期待它无论如何内联调用)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65898215

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档