类型特征以识别可以是 read/written 二进制形式的类型

Type trait to identify types that can be read/written in binary form

是否有类型特征(或概念)来识别以下类型是安全的?

template <typename T>
std::enable_if_t<std::some_type_trait<T>::value> Write(std::ostream &os,const T &x)
  { os.write(reinterpret_cast<const char *>(&x),sizeof(T)); }

template <typename T>
std::enable_if_t<std::some_type_trait<T>::value> Read(std::istream &is,T &x)
  { is.read(reinterpret_cast<char *>(&x),sizeof(T)); }

我在考虑 类 包含 POD,不包括指针(但不包括数组)。类似于 StandardLayoutTypes 但没有指针。我不想将对象限制为 TrivialTypeTriviallyCopyable.

对不起,如果我不准确。我对数据表示知之甚少。

给定 s 的第 1st 个参数,read 方法:

Extracts characters and stores them into successive locations of the character array whose first element is pointed to by s

所以你真正的问题是:如果我通过向对象的地址写入一串字节来初始化对象,它有效吗?

这是 Value Representation. And the value representation of Trivially Copyable 类型的概念:

Copying the bytes occupied by the object in the storage is sufficient to produce another object with the same value

因此你要确保你的对象是可简单复制这不是一个标准的概念,但它可以简洁地定义为:

断言对象至少存在一个 Trivial 初始化程序的精神归结为 Trivially Copyable 类型的这些要求,它是非静态成员,并且它的任何一个都是基础 类:

  1. 给定的 Trivial Initializer 是或表现为相应的默认初始化器
  2. 它没有虚拟方法
  3. 它没有 volatile 限定类型的成员

普通 析构函数的要求而言:

  • The destructor is not user-provided (meaning, it is either implicitly declared, or explicitly defined as defaulted on its first declaration)
  • The destructor is not virtual (that is, the base class destructor is not virtual)
  • All direct base classes have trivial destructors
  • All non-static data members of class type (or array of class type) have trivial destructors

已经完全定义了Trivially Copyable 类型的含义,"type trait or concept" 不可能确定是否在所有情况下都满足所有这些要求,例如:一个定义了 Trivial 初始化器的类型,其签名与默认初始化器匹配,可能是也可能不是 Trivially Copyable 取决于代码它初始化该初始化程序主体中的类型;对于这样的类型,确定它是否 Trivially Copyable 的唯一方法是人工检查初始化程序。但是,如果您愿意将要求收紧到 可检测的范围,is_trivially_copyable 将保证您的类型是 Trivially Copyable.

没有,没有。

如果我们有充分的反思(在 2014 2017 2020 之前达到您附近的 C++ 标准!)您可以编写自己的特征在某种程度上。

但即便如此,您 运行 也遇到了问题。

A std::size_t 可以是一个值,或者是 const char* 的某个散列 table 的索引,由它们的内存位置播种。这样的值 可以安全地写出,然后在下次执行程序时再次读取。

最重要的是,阅读代码可能不同意 int 有多大。所以现在你必须区分 int32_tint,它们在编译器 1 上允许是相同的类型,但在编译器 2 上是不同的类型(甚至编译器设置!)。

你最好的选择是使用你自己的 Koenig flag-function 类型特征,它声称某些东西可以安全地进行二进制序列化,并进行一些可覆盖的安全检查(如果类型不再是 pod、标准布局、可简单复制等)。

除此之外,您应该考虑改用存档系统。添加反射,让你 read/write 的状态达到 object。使聚合 objects 易于递归。

template<class Stream>
void Archive( Stream& s ) {
  s.start(*this)->*[&]{
    s & field1;
    s & field2;
    s & field3;
  };
}

此代码根据 Stream 的类型分为 reader 或编写器引擎,编码 header 和(可选)某种类型标志 *this 和一个长度。然后它会流式传输内容 in/out。最后多余的东西会自动丢弃。

对于二进制类型:

template<class Stream>
void Archive( Stream& s ) {
  FlatBinary( *this, s );
}

为你做了所有这些,但仍然确保大小对齐等(允许结构在以后的修订中增长而不会破坏东西!)我们甚至可以检测平面二进制类型而不必实现 Archive 通过旗帜。

放:

friend std::true_type is_flat_binary_test( BobType ) { return {}; }

在这些类型中。然后做一个

namespace flat_binary_details {
  template<class T>
  inline std::false_type is_flat_binary_test( T ) { return {}; }

  template<class T>
  inline auto flat_binary_f()
  -> decltype( is_flat_binary_test( std::declval<T>() ) )
  { return {}; }
}
template<class T>
using is_flat_binary = decltype( details::flag_binary_f<T>() );

现在,is_flat_binary< std::vector<int> >false_type,而

namespace X {
  struct Bob {
    friend std::true_type is_flat_binary_test( Bob ); // body optional
  };
}
static_assert( is_flat_binary<X::Bob>{}, "Bob is flat!" );

正常工作。

您的存档系统可以测试标记为平面二进制文件的内容,并为它们实施高效的存档系统。没有它的人可以检测到 Archive( Stream& ) 成员并调用它。没有它的它可以检测到 ReadWrite。 Non-default 可扩展的构造类型。 Non-member Achive for std types can be written.

但这太过分了。总之,归档难,找个框架。