9

我有一系列数据结构,应该使用 boost::serialization 从一层传递到上层。例如

struct DataType1
{
    std::string field1;
    std::string field2;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & field1;
        ar & field2;
    }
};

我想为此编写单元测试,以确保我没有错过一些字段(有很多结构和字段)。

问题是,如果我在结构中添加新字段(我肯定会这样做)并且忘记更新单元测试,则该字段将不会被单元测试覆盖。

我的问题是:如何检测结构(或类)发生了变化。我的想法是使用 static_assert(sizeof(DataType1) == HARD_CODED_VALUE) 但它在不同的编译器、平台(x64、x86)和配置(发布、调试)中存在结构大小的差异。

任何好主意如何处理这个?

4

7 回答 7

3

问题是,如果我在结构中添加新字段(我肯定会这样做)并且忘记更新单元测试,则该字段将不会被单元测试覆盖。

我的问题是:如何检测结构(或类)发生了变化。

我的想法是使用 static_assert(sizeof(DataType1) == HARD_CODED_VALUE) [...]

这不是一个可移植的解决方案(正如您自己指出的那样)。

任何好主意如何处理这个?

是的:您可以从更新测试开始吗?

也就是说,不要决定结构中应该包含什么,然后添加它,然后更新测试(如果你没有忘记的话)。

相反,更新测试以检查新的序列化数据,然后确保更新的测试失败,然后才更新代码以使测试通过。

这种方法(首先编写/更新单元测试)已经创建(部分)来解决这个问题。

测试优先方法还有其他优点:

  • 它巧妙地避免了YAGNI

  • 它最大限度地减少了过早的优化

  • 它自然地演变为跟踪您的应用程序/实现的功能完整性。

于 2013-05-29T11:05:55.720 回答
2

在类定义中添加注释以提醒您在添加成员时必须调整序列化程序。计算机可以为您做的事情是有限度的——这就是代码审查很重要的原因。让其他程序员审查任何补丁,拥有一组严格的测试用例并希望获得最好的结果。

我相信你可以编写一个 clang 插件,它可以确保特定方法引用结构的每个成员,但是你真的需要这个吗?你能把时间花在那个上面吗?

也就是说,您可以通过尝试将尽可能多的工作卸载到计算机上来获得奖励积分。即使是static_assert技巧也是一个好方法。如果你为一个特定的 ABI 和你经常构建的架构使用一组#ifdefs 来保护它,它可能会做得很好。

于 2013-05-29T10:21:18.713 回答
1

我有一个类似的问题,我找到了使用 boost::fusion 的解决方案。在这里,您可以遍历结构的所有成员。因此,无需再手动执行任何操作。您还可以获得编译时自省的好功能。所以很容易打印完整结构的内容,例如。通过一个小模板代码进入一个日志文件。

于 2013-05-29T11:52:37.897 回答
1

怎么样:

// DataType1_members.h

FIELD_DEF(std::string, field1);
FIELD_DEF(std::string, field2);

// DataType1.h

struct DataType1
{
#define FIELD_DEF(type, name) type name
    #include "DataType1_members.h"
#undef FIELD_DEF

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
#define FIELD_DEF(type, name) ar & name
    #include "DataType1_members.h"
#undef FIELD_DEF
    }
};

这样你只需要在一个地方添加字段。

于 2013-05-29T11:10:12.667 回答
0

在您的单元测试中,您可以使用预期的 struc 大小进行 static_assert 检查:

static_assert( sizeof(DataType1)==16, "Structure changed. Update serialize method" );

您必须为每个平台(或仅针对一个平台)设置结构的大小(检查中的数字)。

于 2013-05-29T10:40:49.487 回答
0

您可以将静态变量“版本”添加到您的结构中,并在结构更改时增加它。

static int version = 1234;

然后在你的测试中只写

static_assert( DataType1::version == HARD_CODE_VALUE );

但是你仍然可以在更改结构时忘记更新版本,或者在更新测试时忘记添加一些新成员。

于 2013-05-29T10:43:05.870 回答
0

craay 方法是不直接使用成员。

创建一个聚合可变参数模板。创建数据成员模板。

数据成员模板采用标签结构。

覆盖data_member<tag,T>::operator^( tag )以返回对 的引用T。Mqybe 免费做同样的事情operator^( data_member< tag, T >*, tag )

现在您可以通过 获取会员this^tag(),这看起来像是会员访问。如果你创建一个全局实例,tag你甚至可以删除().

您还可以对数据成员进行编译时反射,因此您可以编写for_each_member和编写所有序列化代码一次,并将其用于每个struct.

访问控制和其他类别的data_member可以在aggregate模板中完成。

基于标签的数据构造可以使用复杂而精美的aggregate构造函数来完成。

或者,您可以等待真正的反射出现在 C++ 中,可能在十年内。

或者,您可以将您的struct变成一个tuple包装器,并使用类似上述覆盖技巧的东西来开始this^tag基于名称的访问。

如果我们有一个struct foo容器int x, y并且double d我们想要这样做,我们可能会执行以下操作:

// boilerplate
template<typename C, std::size_t idx> struct Tag {};
template<typename C, std::size_t tag_idx>
auto operator^(C&& lhs, Tag<C, tag_idx> const&>)
  -> decltype( std::get<tag_idx>( std::forward<C>(lhs) )
{ return std::get<tag_idx>( std::forward<C>(lhs); }

struct foo:std::tuple< int, int, double > {};
Tag< foo, 0 > x; // another annoying part: need to manually number them
Tag< foo, 1 > y; // we can avoid this via an aggregate trick, but
Tag< foo, 2 > d; // even that isn't all that pretty
int main() {
  foo bar;
  bar^x = 7;
  bar^y = 3;
  bar^d = 3.14;
}

一个(严重的)问题是,两个不同struct的 s 中的两个成员变量共享相同的“命名空间”,如果它们具有相同的名称,则会发生冲突。

于 2013-05-29T12:08:05.940 回答