我正在为嵌入式系统构建代码,并尝试尽可能多地节省二进制空间。
该代码用于解析协议(MQTT 的价值),其中有许多数据包类型,它们都是不同的,但有一些共同的部分。
目前,为了简化代码的编写,我使用这种模式:
template <PacketType type>
struct ControlPacket
{
FixedHeader<type> type;
VariableHeader<type> header;
Properties<type> props;
... and so on...
};
// Specialize for each type
template <>
struct FixedHeader<CONNECT>
{
uint8_t typeAndFlags;
PacketType getType() const { return static_cast<PacketType>(typeAndFlags >> 4); }
uint8 getFlags() const { return 0; }
bool parseType(const uint8_t * buffer, int len)
{
if (len < 1) return false;
typeAndFlags = buffer[0];
return true;
}
...
};
template <>
struct FixedHeader<PUBLISH>
{
uint8_t typeAndFlags;
PacketType getType() const { return static_cast<PacketType>(typeAndFlags >> 4); }
uint8 getFlags() const { return typeAndFlags & 0xF; }
bool parseType(const uint8_t * buffer, int len)
{
if (len < 1) return false;
typeAndFlags = buffer[0];
if (typeAndFlags & 0x1) return false; // Example of per packet specific check to perform
return true;
}
...
};
... For all packet types ...
这是有效的,我现在正试图减少所有这些模板专业化的二进制影响(否则代码几乎重复了 16 次)
所以,我想出了这个范式:
// Store the most common implementation in a base class
struct FixedHeaderBase
{
uint8_t typeAndFlags;
virtual PacketType getType() { return static_cast<PacketType(typeAndFlags >> 4); }
virtual uint8 getFlags() { return 0; } // Most common code here
virtual bool parseType(const uint8_t * buffer, int len)
{
if (len < 1) return false;
typeAndFlags = buffer[0];
return true;
}
virtual ~FixedHeaderBase() {}
};
// So that most class ends up empty
template <>
struct FixedHeader<CONNECT> final : public FixedHeaderBase
{
};
// And specialize only the specific classes
template <>
struct FixedHeader<PUBLISH> final : public FixedHeaderBase
{
uint8 getFlags() const { return typeAndFlags & 0xF; }
bool parseType(const uint8_t * buffer, int len)
{
if (!FixedHeaderBase::parseType(buffer, len)) return false;
if (typeAndFlags & 0x1) return false; // Example of per packet specific check to perform
return true;
}
};
// Most of the code is shared here
struct ControlPacketBase
{
FixedHeaderBase & type;
...etc ...
virtual bool parsePacket(const uint8_t * packet, int packetLen)
{
if (!type.parseType(packet, packetLen)) return false;
...etc ...
}
ControlPacketBase(FixedHeaderBase & type, etc...) : type(type) {}
virtual ~ControlPacketBase() {}
};
// This is only there to tell which specific version to use for the generic code
template <PacketType type>
struct ControlPacket final : public ControlPacketBase
{
FixedHeader<type> type;
VariableHeader<type> header;
Properties<type> props;
... and so on...
ControlPacket() : ControlPacketBase(type, header, props, etc...) {}
};
这工作得很好,可以减少大量使用的二进制代码空间。顺便说一句,我在final
这里使用,因此编译器可以去虚拟化,并且我在没有 RTTI 的情况下进行编译(显然也使用 -Os 并且每个函数都在其自己的部分中被垃圾收集)。
但是,当我检查符号表大小时,我发现析构函数上有很多重复(所有模板实例都在实现一个明显相同(相同二进制大小)或空的析构函数)。
通常,我理解ControlPacket<CONNECT>
需要调用~FixedHeader<CONNECT>()
并且ControlPacket<PUBLISH>
需要调用~FixedHeader<PUBLISH>()
破坏。
然而,由于所有的析构函数都是虚拟的,有没有一种方法ControlPacket
可以避免它们的析构函数的专业化,而是必须ControlPacketBase
虚拟地破坏它们,这样我就不会得到 16 个无用的析构函数,而只有一个?