作者热门文章
- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在为嵌入式系统构建代码,并试图节省尽可能多的二进制空间。
该代码用于解析协议(protocol)(MQTT表示其值(value)),其中有许多数据包类型,并且它们都是不同的,但是共享一些共同的部分。
目前,为了简化代码编写,我使用了以下模式:
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 ...
// 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个无用的析构函数,而只能得到一个?
最佳答案
值得指出的是,这与称为“相同的COMDAT折叠”或ICF的优化有关。这是一个链接器功能,其中相同的功能(即空功能)全部合并为一个。
并非每个链接程序都支持此功能,也不是每个链接程序都愿意这样做(因为该语言表示不同的功能需要不同的地址),但是您的工具链可以具有此功能。这将是快速和容易的。
我将假设您的问题已通过toy example重现:
#include <iostream>
#include <memory>
#include <variant>
extern unsigned nondet();
struct Base {
virtual const char* what() const = 0;
virtual ~Base() = default;
};
struct A final : Base {
const char* what() const override {
return "a";
}
};
struct B final : Base {
const char* what() const override {
return "b";
}
};
std::unique_ptr<Base> parse(unsigned v) {
if (v == 0) {
return std::make_unique<A>();
} else if (v == 1) {
return std::make_unique<B>();
} else {
__builtin_unreachable();
}
}
const char* what(const Base& b) {
return b.what(); // virtual dispatch
}
const char* what(const std::unique_ptr<Base>& b) {
return what(*b);
}
int main() {
unsigned v = nondet();
auto packet = parse(v);
std::cout << what(packet) << std::endl;
}
A::~A
和
B::~B
都具有(多个)列表,即使它们为空且相同。这是
= default
和
final
。
virtual
,那么这些虚假的定义就消失了,我们达到了目标-但是现在,当unique_ptr删除对象时,我们将调用未定义的行为。
shared_ptr
。之所以可行,是因为
shared_ptr
实际上对它的deleteer函数进行类型擦除(请参阅
this question),因此它绝不会通过基址进行删除。换句话说,当您为源自
shared_ptr<T>(u)
的某些
u
制作
T
时,
shared_ptr
直接存储指向
U::~U
的函数指针。
variant
。进行这样的总括式声明并不是很恰当,但是通常,您可以实现较小的代码,甚至可以通过标签分发实现某些加速,因为避免指定vtable和动态分配。
#include <iostream>
#include <boost/variant.hpp>
extern unsigned nondet();
struct Base {
~Base() = default;
};
struct A final : Base {
const char* what() const {
return "a";
}
};
struct B final : Base {
const char* what() const {
return "b";
}
};
typedef boost::variant<A, B> packet_t;
packet_t parse(unsigned v) {
if (v == 0) {
return A();
} else if (v == 1) {
return B();
} else {
__builtin_unreachable();
}
}
const char* what(const packet_t& p) {
return boost::apply_visitor([](const auto& v){
return v.what();
}, p);
}
int main() {
unsigned v = nondet();
auto packet = parse(v);
std::cout << what(packet) << std::endl;
}
std::variant
坚持要生成一些次要的但存在的vtables来实现自身-我觉得这有点违背了这个目的,尽管即使使用了可变的vtables,代码总体上仍然小得多。
what
的最终实现:
what(boost::variant<A, B> const&):
mov eax, DWORD PTR [rdi]
cdq
cmp eax, edx
mov edx, OFFSET FLAT:.LC1
mov eax, OFFSET FLAT:.LC0
cmove rax, rdx
ret
...::what
成员函数,因此,它实际上只是根据变体值选择要返回的字符串文字。
#include <iostream>
#include <boost/variant.hpp>
extern unsigned nondet();
struct Base {
virtual const char* what() const = 0;
~Base() = default;
};
struct A final : Base {
const char* what() const override {
return "a";
}
};
struct B final : Base {
const char* what() const override {
return "b";
}
};
typedef boost::variant<A, B> packet_t;
packet_t parse(unsigned v) {
if (v == 0) {
return A();
} else if (v == 1) {
return B();
} else {
__builtin_unreachable();
}
}
const Base& to_base(const packet_t& p) {
return *boost::apply_visitor([](const auto& v){
return static_cast<const Base*>(&v);
}, p);
}
const char* what(const Base& b) {
return b.what(); // virtual dispatch
}
const char* what(const packet_t& p) {
return what(to_base(p));
}
int main() {
unsigned v = nondet();
auto packet = parse(v);
std::cout << what(packet) << std::endl;
}
to_base
函数,它可以采用一个变体并为您返回通用基接口(interface)。 (在像您这样的层次结构中,每种基础都可以有多个。)
what
,首先将其转换为基类,然后对
what
成员函数执行虚拟分派(dispatch)。
to_base
中指出一次访问的定义:
to_base(boost::variant<A, B> const&):
lea rax, [rdi+8]
ret
Base
继承的,因此根本不必检查任何变体类型标记。
关于c++ - 优化析构函数的大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60745170/
我是一名优秀的程序员,十分优秀!