我正在编写一个Arduino库来包装引脚函数(digitalRead、digitalWrite、analogRead等)。例如,我有一个RegularPin类,它是一个直通类,一个InvertedPin类,它反转引脚逻辑。这是非常有用的,当从带LED的面包车到继电器板,它反转电路逻辑。我只需要换个班。我还有一个用于按钮的DebouncedPin类,它检查用户按下或释放按钮的时间足够长,以便真正按下/释放按钮。
例如模拟引脚:
// 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“类,它将自动多次读取所选的引脚,并且我想将模板化的类堆叠如下:
AveragedPin<cAnalogRegInPin<A0>, UPDATE_ON_READ|RESET_ON_READ> ava0;甚至:
RangeCorrectedPin<AveragedPin<cAnalogRegInPin<A0>,
UPDATE_ON_READ|RESET_ON_READ,RAW_MIN,RAW_MAX,TARGET_RANGE> rcava0;目前,我将嵌套引脚声明为私有成员,因为它不允许在模板声明中使用类对象。但是,每一层嵌套都毫无用处地吃掉堆栈上的几个字节。
我知道我可以在模板声明中使用引用,但我不太明白它是如何工作/应该如何使用的。我的问题看起来像是空成员优化,但在这里似乎不适用。
我觉得这更像是一个C++问题,而不是一个arduino问题,我不是C++专家。我想这涉及到C++的更高级部分。也许我想要的是不可能的,或者只是最近的C++ (20?)修订。
下面是FixedRangeCorrectedPin类的代码。
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>中那样,因为这里涉及的是哪个引脚在编译时是完全已知的。
发布于 2021-01-26 09:43:47
正如您所看到的,我将引号放入模板声明中,因为它在运行时是不会更改的,并且我不希望在分配pin对象时使用这个引号,就像在vanilla代码中一样。
好的,如果引脚号是一个编译时间常数,它通常是对Arduino的,这个位很好。
但是,使AnalogInPin基类抽象(即添加virtual方法)在实践中将使用至少与您节省的每个对象相同的空间,因为不将引脚存储为整数。
细节是特定于实现的,但是运行时多态需要某种方法来计算,对于AnalogInPin*指向的给定派生类对象,要调用哪个版本的虚拟方法,并且需要存储在每个派生类型的对象中。(您可以验证这是正确的,只需检查sizeof(AnalogInPin),并与sizeof进行比较,这是一个没有virtual方法的完全相同的类。
我知道类不可能大小为零但是..。
对于没有数据成员的基类,有一种特例允许它们不具有额外的大小(派生最多的类型的实例必须至少占用一个字节)。它被称为空基类优化。
暂时将嵌套引脚声明为私有成员,因为它不允许在模板声明中使用类对象。但是,每一层嵌套都毫无用处地吃掉堆栈上的几个字节。
我们可以夷平整个系统(理想情况下,也可以删除抽象基,除非您有需要它的非模板代码):
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;,不如将定义更改为
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派生,然后存储不同的引脚类型。实际上,它可以继承基类。
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,则在不需要存储开销的情况下,通过可变引脚数的平均值编辑示例
template <class... PINS>
struct AveragedPins
{
static int read()
{
return (PINS::read() + ...) / sizeof...(PINS);
}
};这不关心参数是什么类型的引脚,只要它有一个静态的read方法。您可以随意堆叠它:
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()语法。
BASE::read()是一个非虚拟化实例方法调用。也就是说,我们在BASE对象上调用read方法的版本,您也可以编写this->BASE::read()。
它是非虚拟化的,因为虽然基类方法是virtual,但我们知道编译时调用的是正确的覆盖,所以不需要虚拟分派。
在最后的示例中,我们停止了使用继承并将方法变为静态的
PIN::read()没有this,也根本没有对象。这在原则上最类似于调用一个免费的C函数,尽管我们让编译器为每个不同的PIN值生成一个新的实例(然后期待它无论如何内联调用)。
https://stackoverflow.com/questions/65898215
复制相似问题