在 Arduino 库中用 C++ 为 类 优化存储 space
Optimizing storage space for classes in C++ in an Arduino library
我正在编写一个 Arduino 库来包装引脚功能(digitalRead
、digitalWrite
、analogRead
等)。例如,我有一个 RegularPin class 是直通,还有一个 InvertedPin class 反转引脚逻辑。当从带有 LED 的面包板到反转电路逻辑的继电器板时,这很有用。我只需要交换 classes。
我还有一个用于按钮的 DebouncedPin class,它检查用户按下或释放的时间是否足以使按钮真正 pressed/released.
模拟引脚示例:
// 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);
}
如您所见,我将 pin 号放在模板声明中,因为它不会在 运行 时更改,并且我不希望 pin 号在分配 pin 对象时占用内存,就像在香草 arduino C 代码中一样。我知道 classes 的大小不能为零,但请继续阅读。接下来,我想编写一个“AveragedPin”class,它将自动多次读取选定的引脚,我想像这样堆叠我的模板化 classes :
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;
我暂时将嵌套的pin声明为私有成员,因为不允许在模板声明中使用class对象。但是,每一层嵌套都会无用地吃掉堆栈上的几个字节。
我知道我可以在模板声明中使用引用,但我不太明白 works/should 是如何使用的。我的问题看起来像空成员优化,但它似乎不适用于这里。
我觉得这更像是一个 C++ 问题,而不是一个 arduino 问题,而且我不是 C++ 专家。我想这涉及到 C++ 的更高级部分。也许我想要的是不可能的,或者只有最近的 C++(20?)修订。
下面是 FixedRangeCorrectedPin 的代码 class。
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 pin' class 成员并像 template <AnalogInPin pin,int rawMin,int rawMax,int targetRange>
那样在模板声明中替换它,因为这里涉及的引脚是完全已知的编译时间。
As you can see, I put the pin number in the template declaration because it is not to be changed at run time and I do not want the pin number to use memory when I allocate a pin object, just like in vanilla arduino C code.
好的,如果引脚号是一个编译时常量,就像 Arduino 通常那样,这个位就可以了。
但是,使 AnalogInPin
基础 class 抽象(即添加 virtual
方法)在实践中将至少使用与您保存的每个对象一样多的 space通过不将 pin 存储为整数。
细节是特定于实现的,但是运行时多态性需要一些方法来弄清楚,对于[=17]指向的给定派生-class对象=],要调用哪个版本的虚拟方法,以及需要存储在派生类型的每个对象中。 (您可以验证这是真的购买,只需检查 sizeof(AnalogInPin)
并与 sizeof
比较,其他方面相同 class,没有 virtual
方法。
I know classes can not be of size zero but ...
没有数据成员的基 classes 有一个特殊情况,允许 它们 没有 extra 大小(最派生类型的实例必须至少占用一个字节)。它被称为空基 class 优化。
For the time being, I declared the nested pin as a private member because it is not allowed to use a class object in the template declaration. But then, each layer of nesting uselessly eats several bytes on the stack.
我们可以将整个东西展平(理想情况下也可以删除抽象基础,除非您有需要它的非模板代码):
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;
。
经过范围校正的 pin 可以类似,但如果在编译时已知,则带有用于标志和 min/max 边界的额外模板参数。
同样,添加到您的问题的 FixedRangeCorrectPin
不需要从 AnalogInPin
派生,然后也存储不同的 pin 类型。其实可以直接继承基class
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;
Edit 可变数量引脚的平均值示例,没有存储开销,假设我们将 virtual
方法更改为 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()
是一个去虚拟化的实例方法调用。
也就是说,我们在 this
对象上调用 BASE
版本的 read
方法。你也可以写 this->BASE::read()
.
它是去虚拟化的,因为虽然 base-class 方法是 virtual
,但我们知道在编译时调用正确的重写,因此不需要虚拟分派。
在最后的示例中,我们停止使用继承并将方法设为静态,PIN::read()
没有 this
并且根本没有对象。
这在原则上与调用自由 C 函数最相似,尽管我们让编译器为每个不同的 PIN
值生成一个新实例(然后期望它内联无论如何打电话)。
我正在编写一个 Arduino 库来包装引脚功能(digitalRead
、digitalWrite
、analogRead
等)。例如,我有一个 RegularPin class 是直通,还有一个 InvertedPin class 反转引脚逻辑。当从带有 LED 的面包板到反转电路逻辑的继电器板时,这很有用。我只需要交换 classes。
我还有一个用于按钮的 DebouncedPin class,它检查用户按下或释放的时间是否足以使按钮真正 pressed/released.
模拟引脚示例:
// 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);
}
如您所见,我将 pin 号放在模板声明中,因为它不会在 运行 时更改,并且我不希望 pin 号在分配 pin 对象时占用内存,就像在香草 arduino C 代码中一样。我知道 classes 的大小不能为零,但请继续阅读。接下来,我想编写一个“AveragedPin”class,它将自动多次读取选定的引脚,我想像这样堆叠我的模板化 classes :
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;
我暂时将嵌套的pin声明为私有成员,因为不允许在模板声明中使用class对象。但是,每一层嵌套都会无用地吃掉堆栈上的几个字节。
我知道我可以在模板声明中使用引用,但我不太明白 works/should 是如何使用的。我的问题看起来像空成员优化,但它似乎不适用于这里。
我觉得这更像是一个 C++ 问题,而不是一个 arduino 问题,而且我不是 C++ 专家。我想这涉及到 C++ 的更高级部分。也许我想要的是不可能的,或者只有最近的 C++(20?)修订。
下面是 FixedRangeCorrectedPin 的代码 class。
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 pin' class 成员并像 template <AnalogInPin pin,int rawMin,int rawMax,int targetRange>
那样在模板声明中替换它,因为这里涉及的引脚是完全已知的编译时间。
As you can see, I put the pin number in the template declaration because it is not to be changed at run time and I do not want the pin number to use memory when I allocate a pin object, just like in vanilla arduino C code.
好的,如果引脚号是一个编译时常量,就像 Arduino 通常那样,这个位就可以了。
但是,使 AnalogInPin
基础 class 抽象(即添加 virtual
方法)在实践中将至少使用与您保存的每个对象一样多的 space通过不将 pin 存储为整数。
细节是特定于实现的,但是运行时多态性需要一些方法来弄清楚,对于[=17]指向的给定派生-class对象=],要调用哪个版本的虚拟方法,以及需要存储在派生类型的每个对象中。 (您可以验证这是真的购买,只需检查 sizeof(AnalogInPin)
并与 sizeof
比较,其他方面相同 class,没有 virtual
方法。
I know classes can not be of size zero but ...
没有数据成员的基 classes 有一个特殊情况,允许 它们 没有 extra 大小(最派生类型的实例必须至少占用一个字节)。它被称为空基 class 优化。
For the time being, I declared the nested pin as a private member because it is not allowed to use a class object in the template declaration. But then, each layer of nesting uselessly eats several bytes on the stack.
我们可以将整个东西展平(理想情况下也可以删除抽象基础,除非您有需要它的非模板代码):
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;
。
经过范围校正的 pin 可以类似,但如果在编译时已知,则带有用于标志和 min/max 边界的额外模板参数。
同样,添加到您的问题的 FixedRangeCorrectPin
不需要从 AnalogInPin
派生,然后也存储不同的 pin 类型。其实可以直接继承基class
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;
Edit 可变数量引脚的平均值示例,没有存储开销,假设我们将 virtual
方法更改为 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()
是一个去虚拟化的实例方法调用。也就是说,我们在
this
对象上调用BASE
版本的read
方法。你也可以写this->BASE::read()
.它是去虚拟化的,因为虽然 base-class 方法是
virtual
,但我们知道在编译时调用正确的重写,因此不需要虚拟分派。在最后的示例中,我们停止使用继承并将方法设为静态,
PIN::read()
没有this
并且根本没有对象。这在原则上与调用自由 C 函数最相似,尽管我们让编译器为每个不同的
PIN
值生成一个新实例(然后期望它内联无论如何打电话)。