我们的软件正在抽象出硬件,并且我们有代表该硬件状态的类,并且具有用于该外部硬件的所有属性的大量数据成员。我们需要定期更新有关该状态的其他组件,为此,我们需要通过MQTT和其他消息传递协议(protocol)发送经过protobuf编码的消息。有不同的消息描述了硬件的不同方面,因此我们需要发送有关这些类数据的不同 View 。这是一个草图:

struct some_data {
  Foo foo;
  Bar bar;
  Baz baz;
  Fbr fbr;
  // ...
};

假设我们需要发送一封包含foobar的消息,以及一封包含barbaz的消息。目前,我们的方法很多:
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)) );
}

我完成了这项工作,并且大大减少了样板。 (不是在这个小例子中,但是如果您想象要对十二个数据点进行编码和解码,那么很多重复出现的数据成员列表的消失将产生很大的不同)。但是,这有两个缺点:
  • 这取决于FooBarBaz是不同的类型。如果它们都是int,我们需要向元组添加一个虚拟标记类型。

    可以做到这一点,但确实会使整个想法失去吸引力。
  • 旧代码中的变量名称是什么,新代码中的注释和数字。这非常糟糕,而且考虑到在编码和解码中可能会出现使两个成员混淆的错误,因此无法在简单的单元测试中发现它,而是需要通过其他技术创建的测试组件(因此集成测试)以捕获此类错误。

    我不知道该如何解决。

  • 有谁有一个更好的主意,如何为我们减少样板?

    注意:
  • 目前,我们只使用C++ 03。是的,你没看错。对我们来说,它是std::tr1::tuple。没有lambda。也没有auto
  • 我们有大量使用这些序列化特征的代码。我们不能抛弃整个计划,而做一些完全不同的事情。我正在寻找一种解决方案,以简化将来的代码以适应现有框架。任何需要我们重新编写整个内容的想法都将被驳回。
  • 最佳答案

    我将以您提出的解决方案为基础,但是使用boost::fusion::tuples代替(假设允许)。假设您的数据类型是

    struct Foo{};
    struct Bar{};
    struct Baz{};
    struct Fbr{};
    

    你的数据是
    struct some_data {
        Foo foo;
        Bar bar;
        Baz baz;
        Fbr fbr;
    };
    

    从这些注释中,我了解到您无法控制SerializedXYZ类,但是它们确实具有特定的接口(interface)。我将假设这样的距离足够近(?):
    struct SerializedFooBar {
    
        void set_foo(const Foo&){
            std::cout << "set_foo in SerializedFooBar" << std::endl;
        }
    
        void set_bar(const Bar&){
            std::cout << "set_bar in SerializedFooBar" << std::endl;
        }
    };
    
    // another protobuf-generated class
    struct SerializedBarBaz {
    
        void set_bar(const Bar&){
            std::cout << "set_bar in SerializedBarBaz" << std::endl;
        }
    
        void set_baz(const Baz&){
            std::cout << "set_baz in SerializedBarBaz" << std::endl;
        }
    };
    

    现在,我们可以减少样板并将其限制为每个datatype-permutation一个typedef,以及SerializedXYZ类的每个set_XXX成员一个简单的重载,如下所示:
    typedef boost::fusion::tuple<Foo, Bar> foobar;
    typedef boost::fusion::tuple<Bar, Baz> barbaz;
    //...
    
    template <class S>
    void serialized_set(S& s, const Foo& v) {
        s.set_foo(v);
    }
    
    template <class S>
    void serialized_set(S& s, const Bar& v) {
        s.set_bar(v);
    }
    
    template <class S>
    void serialized_set(S& s, const Baz& v) {
        s.set_baz(v);
    }
    
    template <class S, class V>
    void serialized_set(S& s, const Fbr& v) {
        s.set_fbr(v);
    }
    //...
    

    现在的好处是,您不再需要专门化您的serialization_traits。下面利用了boost::fusion::fold函数,我认为可以在您的项目中使用它:
    template <class SerializedX>
    class serialization_traits {
    
        struct set_functor {
    
            template <class V>
            SerializedX& operator()(SerializedX& s, const V& v) const {
                serialized_set(s, v);
                return s;
            }
        };
    
    public:
    
        template <class Tuple>
        static SerializedX encode(const Tuple& t) {
            SerializedX s;
            boost::fusion::fold(t, s, set_functor());
            return s;
        }
    };
    

    这里是一些工作原理的例子。请注意,如果有人尝试从不符合SerializedXYZ接口(interface)的some_data中绑定(bind)数据成员,则编译器会通知您:
    void send_msg(const SerializedFooBar&){
        std::cout << "Sent SerializedFooBar" << std::endl;
    }
    
    void send_msg(const SerializedBarBaz&){
        std::cout << "Sent SerializedBarBaz" << std::endl;
    }
    
    void send(const some_data& data) {
      send_msg( serialization_traits<SerializedFooBar>::encode(boost::fusion::tie(data.foo, data.bar)) );
      send_msg( serialization_traits<SerializedBarBaz>::encode(boost::fusion::tie(data.bar, data.baz)) );
    //  send_msg( serialization_traits<SerializedFooBar>::encode(boost::fusion::tie(data.foo, data.baz)) ); // compiler error; SerializedFooBar has no set_baz member
    }
    
    int main() {
    
        some_data my_data;
        send(my_data);
    }
    

    代码here

    编辑:

    不幸的是,此解决方案无法解决OP的问题#1。为了解决这个问题,我们可以定义一系列标签,为您的每个数据成员使用一个标签,并采用类似的方法。以下是标签以及修改后的serialized_set函数:
    struct foo_tag{};
    struct bar1_tag{};
    struct bar2_tag{};
    struct baz_tag{};
    struct fbr_tag{};
    
    template <class S>
    void serialized_set(S& s, const some_data& data, foo_tag) {
        s.set_foo(data.foo);
    }
    
    template <class S>
    void serialized_set(S& s, const some_data& data, bar1_tag) {
        s.set_bar1(data.bar1);
    }
    
    template <class S>
    void serialized_set(S& s, const some_data& data, bar2_tag) {
        s.set_bar2(data.bar2);
    }
    
    template <class S>
    void serialized_set(S& s, const some_data& data, baz_tag) {
        s.set_baz(data.baz);
    }
    
    template <class S>
    void serialized_set(S& s, const some_data& data, fbr_tag) {
        s.set_fbr(data.fbr);
    }
    

    与我之前的回答类似,样板文件又被限制为每个数据成员一个serialized_set并线性缩放。这是修改后的serialization_traits:
    // the serialization_traits doesn't need specialization anymore :)
    template <class SerializedX>
    class serialization_traits {
    
        class set_functor {
    
            const some_data& m_data;
    
        public:
    
            typedef SerializedX& result_type;
    
            set_functor(const some_data& data)
            : m_data(data){}
    
            template <class Tag>
            SerializedX& operator()(SerializedX& s, Tag tag) const {
                serialized_set(s, m_data, tag);
                return s;
            }
        };
    
    public:
    
        template <class Tuple>
        static SerializedX encode(const some_data& data, const Tuple& t) {
            SerializedX s;
            boost::fusion::fold(t, s, set_functor(data));
            return s;
        }
    };
    

    这是它的工作方式:
    void send(const some_data& data) {
    
        send_msg( serialization_traits<SerializedFooBar>::encode(data,
        boost::fusion::make_tuple(foo_tag(), bar1_tag())));
    
        send_msg( serialization_traits<SerializedBarBaz>::encode(data,
        boost::fusion::make_tuple(baz_tag(), bar1_tag(), bar2_tag())));
    }
    

    更新了代码here

    10-08 09:29