前言:
MP全称: Meta Program。文中将以MP简称来代替。
想必大家对C++封装思想(OOP)已经非常熟悉了,这里不在阐述OOP下的类设计。毕竟本文的重点是将从另一个角度(MP)来重设计C++类, 让大家感受下C++语言的多元化的魅力。
本文站在MP实践立场,从C++ MP对于实际编程、类设计的作用出发,跟大家探讨在实际开发中MP对于类设计的扩展、优化等。
正文:
相信在平时业务开发中一般遇到或设计过IPC、网络(http)、上下位机器这些需要定义自定义消息或根据特定协议结构来声明消息Model 类。本文将以自定义IPC 消息示例来进行。
说明: 本文所有示例代码仅作MP对类进行重设计的演示用,部分逻辑或类声明并非非常严谨,仅用作能够清晰的表达出文中使用MP对类设计作用的意图, 同时代码基于C++17标准。
开始吧,首先对IPC消息结构建模,用于我们在文中进行探讨的示例类。
class IPCMessage {
public:
int version; // 消息版本号
std::string type; // 消息类型
using Header = std::unordered_map<std::string, std::string>;
Header headers; // 消息头内容
std::string payload; // 消息内容
};
void SerializationMessage(std::ostream& stream, const IPCMessage & msg) {
int total_size = 16 + msg.type.size();
std::string headers;
for (const auto& [key, value] : msg.headers) {
headers += ((headers.empty() ? "": "&") + key + "&" + headers)
}
total_size += headers.size();
total_size += msg.payload.size();
stream << total_size
<< msg.version
<< msg.type.size()
<< msg.type.data()
<< headers.size()
<< headers.data()
<< msg.payload.data();
}
这是基于OOP思想设计的一个普通IPCMessage Model类。 开始发现不足:
这里我们很快发现传统IPCMessage model字段中的string字段 无法修改allocator。出于性能考虑IPCMessage类的所有的string成员字段内容并非大小大致相同。我们需要能够设置字段分配器类型,用于能够为每个需要分配器字段的成员设置分配器的类型。想想我们该如果修改IPCMessage model的声明?解决方案是为IPCMessage Model类添加分配器模板参数用于设置需要分配器类型的字段, 这是能最快解决问题的方式。代码如下图所示:
template<class Allocator,
class StrAllocator=Allocator,
class MapKVAllocator=Allocator,
class PayloadAllocator=Allocator>
class IPCMessage {
public:
int version; // 消息版本号
basic_string<char, char_traits<char>, StrAllocator> type; // 消息类型
using MAPKVString = basic_string<char, char_traits<char>, MapKVAllocator>;
using Header = std::unordered_map<MAPKVString,
MAPKVString,
hash<MAPKVString>,
equal_to<MAPKVString>,
allocator<pair<const MAPKVString, MAPKVString>>;
Header headers; // 消息头内容
basic_string<char, char_traits<char>, PayloadAllocator> payload; // 消息内容
};
看起来可以工作,对于大小大致相同的string字段使用了相同的分配器,无论是从代码可读性、效率上来说它都没有错,但对payload字段使用了独立的分配器因为我们无法确认消息内容的大小。但目前来看IPCMessage Model类它确实太复杂了。估计没人会喜欢这样的代码。 对于需要配置Allocator目前来看确实是有些困难。不妨我们先开看看对于payload字段的优化,或许从它可以得到一些启发。现在来谈谈 payload的字段的重构, 解决方案是我们需要一个间接的中间层来解决复杂问题。我们把Payload类型提取出来作为IPCMessage Model的模板参数,IPCMessage升级为模板类了。优化后的IPCMessage Model如下所示:
template<class Payload>
class IPCMessage {
public:
int version; // 消息版本号
std::string type; // 消息类型
using Header = std::unordered_map<std::string, std::string>;
Header headers; // 消息头内容
Payload payload; // 消息内容
};
template<class Payload>
void SerializationMessage(std::ostream& stream, const IPCMessage<Payload> & msg)
{
int total_size = 16 + msg.type.size();
std::string headers;
for (const auto& [key, value] : msg.headers) {
headers += ((headers.empty() ? "&