是否有针对新成员的编译器强化实施完整性的模式

Is there a pattern for compiler-enforced implementation completeness regarding a new member?

本文关键字:模式 完整性 编译器 新成员 是否      更新时间:2023-10-16

想象一个较大的项目,其中包含一些参数结构:

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
};

是否有设计模式可以打破构建或发出警告,直到适应所有必要代码?

示例:

  • 如果我要将int foo更改为string foo,我不会错过任何需要更改的代码行。
  • 如果int foo需要更改为unsigned int foo,我可以将foo重命名为foo_u,并将编译器指向我的适应性。

一个部分解决方案是使成员private并仅从构造函数中进行设置,该构建器必须用所有参数调用:

pars::pars(int _foo, int _bar)
 : foo(_foo), bar(_bar)
{ }

这确保了PAR的正确创建,但不能使用用法 - 因此,这会在FromString()中捕获丢失的适应,但在ToString()中却没有。

单元测试只会在测试期间揭示此类问题(我正在寻找编译时间方法),也只有(DE)序列化部分,而不是在任何地方都考虑到新的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;
}

还可以查看:

boost.serialization http://www.boost.org/doc/libs/1_64_0/libs/serialization/doc/index.html

谷物https://github.com/uscilab/cereal

等。

强制执行此操作的模式。

选择一个名称,例如members_of。使用ADL和标签,使members_of(tag<T>)返回整体恒定成员指针的元组,向T的成员。

这必须写一次。然后可以使用许多斑点。

我会在14岁时将其写入C 17中,而更早的详细信息。

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> );
  }
};

当您添加成员时,还必须将其添加到朋友members_of

template<class...Fs>
struct overload:Fs...{
  using Fs::operator()...;
  overload(Fs...fs):Fs(std::move(fs))... {}
};

overload让您超载lambdas。

终于写一个foreach_tuple_element。

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;
}

当您向parsmembers_of添加新成员bar时,上述代码会断开,因为foreach找不到val_t<&pars::bar>的过载。

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;
}

现在它将编译。


针对序列化/次要化,您需要两个方法(一个Arg的类型在内外或外说),而字符串for/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);
  });
}

这是统一序列化/挑选方法(没有上述成员指针)的示例。您在输出流和primitive const&上覆盖Archive,在输入流和primitive&上覆盖。

对于几乎所有其他所有内容,您都会使用共同的结构来读取和从档案中写作。这使您的输入结构保持相同。

ArchiveBlock( Archive&, tag, tag version, lambda )lambda包裹在您拥有的任何归档块结构中。例如,您的档案块可能在其标题中包含长度信息,从而使较早的Deserializer可以在最后跳过添加的数据。它也会读写块;在写作时,它会在编写身体之前写出块标头和其他任何内容(可能会跟踪长度并备份到记录长度后,一旦他们知道了)。在阅读时,它将确保该标签存在(并选择您选择丢失的标签;跳过?),如果您想支持较新的读者阅读更新的作家写的内容。


在您需要保持代码与数据保持一致的更一般情况下,此答案可能会解决问题。序列化和挑选化是非常特殊的情况,因为与大多数C 代码不同,您必须将未来的二进制布局 exthert 的二进制布局。就像写库界面一样;需要更多的护理。