我们的软件正在抽象化硬件,我们有代表这些硬件状态的类,并拥有所有外部硬件属性的许多数据成员。我们需要定期更新其他组件的状态,为此我们通过MQTT和其他消息传递协议发送protobuf编码的消息。有不同的消息描述硬件的不同方面,因此我们需要发送这些类的不同数据视图。下面是一个草图:
struct some_data {
Foo foo;
Bar bar;
Baz baz;
Fbr fbr;
// ...
};
假设我们需要发送两个消息,一个包含foo
和bar
,另一个包含bar
和baz
。我们现在的做法是很繁琐的:
struct foobar {
Foo foo;
Bar bar;
foobar(const Foo& foo, const Bar& bar) : foo(foo), bar(bar) {}
bool operator==(const foobar& rhs) const {return foo == rhs.foo && bar == rhs.bar;}
bool operator!=(const foobar& rhs) const {return !operator==(*this,rhs);}
};
struct barbaz {
Bar bar;
Baz baz;
foobar(const Bar& bar, const Baz& baz) : bar(bar), baz(baz) {}
bool operator==(const barbaz& rhs) const {return bar == rhs.bar && baz == rhs.baz;}
bool operator!=(const barbaz& rhs) const {return !operator==(*this,rhs);}
};
template<> struct serialization_traits<foobar> {
static SerializedFooBar encode(const foobar& fb) {
SerializedFooBar sfb;
sfb.set_foo(fb.foo);
sfb.set_bar(fb.bar);
return sfb;
}
};
template<> struct serialization_traits<barbaz> {
static SerializedBarBaz encode(const barbaz& bb) {
SerializedBarBaz sbb;
sfb.set_bar(bb.bar);
sfb.set_baz(bb.baz);
return sbb;
}
};
这可以随后被发送:
void send(const some_data& data) {
send_msg( serialization_traits<foobar>::encode(foobar(data.foo, data.bar)) );
send_msg( serialization_traits<barbaz>::encode(barbaz(data.foo, data.bar)) );
}
考虑到要发送的数据集通常比两个项目大得多,我们还需要解码这些数据,并且我们有很多这样的消息,因此涉及到的模板代码比这个示意图中所示的要多得多。因此,我一直在寻找一种减少这种情况的方法。这是一个最初的想法:
typedef std::tuple< Foo /* 0 foo */
, Bar /* 1 bar */
> foobar;
typedef std::tuple< Bar /* 0 bar */
, Baz /* 1 baz */
> barbaz;
// yay, we get comparison for free!
template<>
struct serialization_traits<foobar> {
static SerializedFooBar encode(const foobar& fb) {
SerializedFooBar sfb;
sfb.set_foo(std::get<0>(fb));
sfb.set_bar(std::get<1>(fb));
return sfb;
}
};
template<>
struct serialization_traits<barbaz> {
static SerializedBarBaz encode(const barbaz& bb) {
SerializedBarBaz sbb;
sfb.set_bar(std::get<0>(bb));
sfb.set_baz(std::get<1>(bb));
return sbb;
}
};
void send(const some_data& data) {
send_msg( serialization_traits<foobar>::encode(std::tie(data.foo, data.bar)) );
send_msg( serialization_traits<barbaz>::encode(std::tie(data.bar, data.baz)) );
}
我已经成功实现了这个功能,并且大大减少了样板代码。(在这个小例子中没有体现,但是如果你想象一下十几个数据点被编码和解码,很多重复的数据成员列表消失会有很大的区别)。然而,这种方法有两个缺点:
这依赖于
Foo
、Bar
和Baz
是不同的类型。如果它们都是int
,我们需要向元组中添加一个虚拟标记类型。虽然可以做到,但这确实使整个想法变得不太吸引人。
旧代码中的变量名变成了新代码中的注释和数字。这很糟糕,考虑到编码和解码中很可能存在混淆两个成员的错误,不能通过简单的单元测试来捕获,而需要通过其他技术创建测试组件(即集成测试)来捕获此类错误。
我不知道如何解决这个问题。
有没有人有更好的想法来减少我们的样板代码?
注意:
- 目前,我们被困在C++03中。是的,你没看错。对我们而言,它是
std::tr1::tuple
。没有lambda,也没有auto
。 - 我们有大量的代码使用这些序列化特性。我们不能抛弃整个方案并完全采用其他方法。我正在寻求一个解决方案,简化适合现有框架的未来代码。任何需要我们重新编写整个系统的想法很可能会被驳回。
foo
和bar
合并为一个foobar消息?这些数据集是否有关联?对我来说,它看起来更像是样板代码中不必要的数据组合!此外,您能否稍微更改数据结构(struct Foo、Bar等),例如添加一个函数?那么如何解码这些消息呢? - user1810087SerializedXY
类型,通过简化的接口(您选择的是serialization_traits
的特化)来访问它们。这是一个公平的总结吗?从这个角度来看,你尝试的东西作为你正在寻找的东西的例子是有用的,但更有用的是关于这些SerializedXY
类型的信息。你能否在问题中添加一些关于它们的信息,比如它们是如何生成的(为什么不能改变),以及它们的公共接口是什么? - JaMiT