C++ 对于新成员,是否存在编译器强制实现完整性的模式?
想象一个更大的项目,包含一些参数结构:C++ 对于新成员,是否存在编译器强制实现完整性的模式?,c++,compilation,C++,Compilation,想象一个更大的项目,包含一些参数结构: struct pars { int foo; }; 使用此结构作为参数,可以实现其他功能,例如: // (de)serialization into different formats static pars FromString(string const &text); static string ToString(pars const &data); static pars FromFile(string const
struct pars {
int foo;
};
使用此结构作为参数,可以实现其他功能,例如:
// (de)serialization into different formats
static pars FromString(string const &text);
static string ToString(pars const &data);
static pars FromFile(string const &filename);
// [...]
// comparison / calculation / verification
static bool equals(pars l, pars r);
static pars average(pars a, pars b);
static bool isValid(pars p);
// [...]
// you-name-it
现在,假设需要向该结构添加一个新成员:
struct pars {
int foo;
int bar; // new member
};
是否存在一种设计模式来中断构建或发出警告,直到所有必要的代码位置都被修改
示例:
- 如果我将
更改为intfoo
,我不会错过任何需要更改的代码行stringfoo
- 如果
需要更改为int-foo
,我可以将unsigned int-foo
重命名为foo
,并让编译器指出需要进行修改的地方foo\u
私有
且仅可从构造函数进行设置,必须使用所有参数调用构造函数:
pars::pars(int _foo, int _bar)
: foo(_foo), bar(_bar)
{ }
这确保了PAR的正确创建,而不是用法-因此这将捕获FromString()
中缺少的自适应,而不是ToString()
中缺少的自适应
单元测试只会在测试期间(我正在搜索编译时方法)发现此类问题,而且只会发现(反)序列化部分,而不是到处都在考虑新的
bar
(在比较/计算/验证/…函数中).将流操作与流的源或目标分离
一个非常简单的例子:
#include <sstream>
#include <fstream>
struct pars
{
int foo;
int bar;
static constexpr auto current_version = 2;
};
std::istream &deserialise(std::istream &is, pars &model)
{
int version;
is >> version;
is >> model.foo;
if (version > 1) {
is >> model.bar;
}
return is;
}
std::ostream &serialise(std::ostream &os, const pars &model)
{
os << model.current_version << " ";
os << model.foo << " ";
// a version 2 addition
os << model.bar<< " ";
return os;
}
static pars FromString(std::string const &text)
{
std::istringstream iss(text);
auto result = pars();
deserialise(iss, result);
return result;
}
static std::string ToString(pars const &data)
{
std::ostringstream oss;
serialise(oss, data);
return oss.str();
}
static pars FromFile(std::string const &filename)
{
auto file = std::ifstream(filename);
auto result = pars();
deserialise(file, result);
return result;
}
#包括
#包括
结构部
{
int foo;
int-bar;
静态constexpr自动当前版本=2;
};
std::istream&反序列化(std::istream&is、PAR&model)
{
int版本;
是>>版本;
is>>model.foo;
如果(版本>1){
is>>model.bar;
}
回报是;
}
标准::ostream和serialise(标准::ostream和os、const-pars和model)
{
os强制执行此操作的模式将是针对每个成员的操作
选择一个名称,如members\u of
。使用ADL和标记,使members\u of(tag)
返回指向T
成员的整型常量成员指针元组
这必须写一次,然后可以在很多地方使用
我将用C++17编写它,就像在14和更早版本中一样,它只是更加冗长而已
template<class T>struct tag_t{constexpr tag_t(){}};
template<class T>constexpr tag_t<t> tag{};
template<auto X>using val_t=std::integral_constant<decltype(X), X>;
template<auto X>constexpr val_k<X> val{};
struct pars {
int foo;
friend constexpr auto members_of( tag_t<pars> ){
return std::make_tuple( val<&pars::foo> );
}
};
重载
允许您重载lambda
最后编写一个foreach\u tuple\u元素
static pars FromString(string const &text){
pars retval;
foreach_tuple_element( members_of(tag<pars>), overload{
[&](val_t<&pars::foo>){
// code to handle pars.foo
}
});
return retval;
}
现在它可以编译了
具体地说,对于序列化/反序列化,两种方法都需要一个方法(其中一个参数的类型表示它是in还是out),而字符串to/from只是序列化/反序列化的一种特殊情况
template<class A, class Self,
std::enable_if_t<std::is_same<pars, std::decay_t<Self>>{}, int> =0
>
friend void Archive(A& a, Self& self) {
ArchiveBlock(a, archive_tag("pars"), 3, [&]{
Archive(a, self.foo);
Archive(a, self.bar);
});
}
模板
好友无效存档(A&A、Self和Self){
归档块(a,归档标签(“PAR”),3、[&]{
档案(a,self.foo);
档案馆(一个自助酒吧);
});
}
这是一个统一的序列化/反序列化方法(没有上述成员指针)工作原理的示例。您可以覆盖输出流和输入流上的primitive const&
、以及primitive&
上的Archive
对于几乎所有其他内容,您都使用公共结构来读取和写入归档文件。这样可以保持输入和输出的结构相同
ArchiveBlock(归档和标记,标记版本,lambda)
将lambda
包装在您拥有的任何存档块结构中。例如,您的存档块的头中可能有长度信息,允许早期的反序列化程序跳过最后添加的数据。它还可以读取和写入块;写入时,它将写出块头以及之前的任何内容重新写入正文(可能会跟踪长度,并在知道长度后备份以记录长度)。在阅读时,它将确保标记存在(并根据您的选择处理丢失的标记;跳过?),如果您希望支持较老的读者阅读较新的作者所写的内容,则可以在较新的块内容上快进
在更一般的情况下,你需要保持代码与数据对齐。这个答案可能会解决问题。序列化和反序列化是非常特殊的情况,因为与C++代码的大多数位不同,你必须对所有事物的二进制布局进行验证。这就像编写库接口;需要更多的关心。G最匹配的标签,或者更好的标题。已经通过boost.serialization和许多其他方法解决了。使用constepr
版本启发我认为OP可以在(de)中编写静态断言序列化函数在版本号上。@AndyG这里的想法是,软件检查允许更高版本读取由早期版本编写的存档,并提供适当的默认值。问题主要不是序列化(请参见编辑)。但我喜欢constepr
版本成员的想法(虽然static const int
即使在C+99中也是一个constepr)与static_assert
结合使用:它添加了很少的额外代码,没有模板魔法,而且是不言自明的。缺点:人们很容易“忘记”将static_assert
添加到自己的代码中。问题主要不是关于序列化的(请参见编辑)。只是感兴趣:你能详细说明一下你想要一个方法来处理这两个问题吗?@MartinHennings
static pars FromString(string const &text){
pars retval;
foreach_tuple_element( members_of(tag<pars>), overload{
[&](val_t<&pars::foo>){
// code to handle pars.foo
},
[&](val_t<&pars::bar>){
// code to handle pars.bar
}
});
return retval;
}
template<class A, class Self,
std::enable_if_t<std::is_same<pars, std::decay_t<Self>>{}, int> =0
>
friend void Archive(A& a, Self& self) {
ArchiveBlock(a, archive_tag("pars"), 3, [&]{
Archive(a, self.foo);
Archive(a, self.bar);
});
}