【全网首发】心心念念的优化完成了,虽然不是很完美原创
你好,我是雨乐!
核心模块有个功能点,一直以来想着将其优化掉(虽然在线上稳定运行了这么多年),要么没时间,要么懒的搞,一拖再拖。期间也想了各种方案,无奈不是很完美,恰好吴老师进群了,随向有着20多年经验的吴老师进行了请教,也跟A总,E总等进行了讨论,慢慢的也有了优化思路,于是用了大概一天的时间,基于这几个大佬的方案,进行了优化。
需求
项目中有这样一个需求,根据一个类别名以及其对应的类型,创建对应的数据结构。需求很简单吧。。。
优化前的版本,先创建一个配置,然后程序启动的时候,加载跟配置,然后根据配置内容进行相应的操作。
配置如下:
config.json
{
"phone_brand":"string",
"gender":"int"
}
解析如下:
// parse config.json
if (pare("config.json").failed()) {
return;
}
for (const auto & item : parse.elements()) {
auto name = item.first();
auto type = item.second();
if (type == "string") {
mp[name] = std::make_shared<Session<std::string>>(name);
} else if (type == "int") {
mp[name] = std::make_shared<Session<int>>(name);
}
}
其实,说实话,如果没有洁癖的话,这段代码也不是不可行😁。也线上稳定运行了几年,不过,多少感觉这种方式很傻瓜,就像要求写一个算法,而自己实现了一个冒泡一样,虽然功能满足,但方案并不优雅。
方案一: typelist
既然需求是根据字符串类型来创建对应的数据类型,那么不妨把各种数据类型结合起来,而支持多种数据类型的,对于这种多类型的,第一时间想到了std::variant和std::tuple,不过因为std::variant使用上的限制以及实现本功能的话需要增加很多判断代码,所以最终选择了std::tuple来实现:
using types = std::tuple<int, double, std::string, int64_t>;
template<std::size_t N>
using StrType = typename std::tuple_element<N, types>::type;
constexpr bool strings_equal(const char* a, const char* b) {
return *a == *b && (*a == '\0' || strings_equal(a + 1, b + 1));
}
constexpr std::size_t getIdx(const char* name)
{
return strings_equal(name, "int") ? 0:
strings_equal(name, "double") ? 1:
strings_equal(name, "std::string") ? 2:
strings_equal(name, "int64_t") ? 3 : 4;
}
int main() {
if (pare("config.json").failed()) {
return;
}
for (const auto & item : parse.elements()) {
auto name = item.first();
auto type = item.second();
mp[name] = std::make_shared<Session<StrType<getIdx(type)>>>(name);
}
}
在上述中,依然采取配置文件的方式,创建了一个支持int、string等类型的std::tuple,并通过getIdx和strings_equal来获取该类型在tuple中的index,进而创建相应的类型。
其实,如果把该方案跟现有实现(第一个)相比较的话,并没有变得多优雅,反而多了很多代码。。。
方案二: reflection
其实,这种需求从概念上讲,应该是reflection,中文称为反射,众所周知C++标准委员会那帮人不食人间烟火,也一直没有将反射纳入标准。这块也在群里进行了讨论,也聊了java中反射的实现机制和其弊端。其间,吴老师也发表了相关意见,原来对于反射这块,于13年就专门成立了反射研究组,只是一直没达到能进标准的共识。具体可以参考静态反射。
如果对需求进行重构的话,我的需求也比较简单,就是一个struct,里面有各种变量,需要实现一个功能,就是获取struct中的变量list以及对应的变量内容:
struct Config {
int a;
std::string b;
};
void fun() {
Config cfg;
std::vector<Filed> fileds = GetFileds(cfg);
for (const auto & item : fileds) {
auto name = item.name();
auto c = std::make_shared<item::type>();
// do other sth
}
}
于是在gayhub上也调研了实现,没有一个特别满意的方法,因为项目中大量用到了pb,所以借助pb的反射功能来进行实现:
message Config {
int32 a;
string b;
}
Config category;
const google::protobuf::Descriptor* desc = category.GetDescriptor();
std::vector<const google::protobuf::FieldDescriptor *> vfd;
for (int i = 0; i < desc->field_count(); ++i) {
const google::protobuf::FieldDescriptor* fd = desc->field(i);
const auto &category_name = fd->name();
switch(fd->cpp_type()) {
case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
auto name = item.name();
auto c = std::make_shared<std::string>();
break;
}
case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: {
auto name = item.name();
auto c = std::make_shared<uint32_t>();
break;
}
default:
break;
}
}
上面这块借助于pb也基本满足了需求,唯一不足的是需要有这个switch case,需要将pb的type转换成cpp支持的type,不过不过怎么说,也比现在线上的方式要优雅的多。
今天的文章就到这,我们下期见!
你好,我是雨乐,从业十二年有余,历经过传统行业网络研发、互联网推荐引擎研发,目前在广告行业从业8年。 目前任职某互联网公司高级技术专家一职,负责广告引擎的架构和研发。