输入特征以识别可以二进制形式读取/写入的类型

时间:2016-11-10 11:56:04

标签: c++ c++11 binaryfiles traits

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

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的类,不包括指针(但不包括数组)。像StandardLayoutType s但没有指针的东西。我既不想将对象约束为TrivialType也不要TriviallyCopyable

很抱歉,如果我不准确的话。我对数据表示知之甚少。

2 个答案:

答案 0 :(得分:5)

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

  

提取字符并将它们存储到字符数组的连续位置,其第一个元素由s指向

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

这是Value Representation的概念。 Trivially Copyable类型的值表示如下:

  

复制存储中对象占用的字节足以生成具有相同值的另一个对象

因此,您希望确保您的对象 Trivially Copyable 这不是一个标准概念,但它可以简洁地定义为:

  

对于对象存在至少一个 Trivial 初始值设定项的断言的精神归结为 Trivially Copyable 类型的这些要求,它是非静态成员及其任何基类:

  1. 给定 Trivial 初始化程序是或相应的默认初始化程序
  2. 它没有虚拟方法
  3. 它没有挥发性合格类型的成员
  4. 至于 Trivial 析构函数的要求:

      
        
    • 析构函数不是用户提供的(意思是,它是隐式声明的,或者在其第一个声明中明确定义为默认值)
    •   
    • 析构函数不是虚拟的(即基类析构函数不是虚拟的)
    •   
    • 所有直接基类都有简单的析构函数
    •   
    • 类类型(或类类型数组)的所有非静态数据成员都有简单的析构函数
    •   

    完全定义了 Trivially Copyable 类型的含义,对于&#34;类型特征或概念&#34;是不可能的。确定是否在所有情况下都满足所有这些要求,例如:定义具有与默认初始化程序匹配的签名的 Trivial 初始化程序的类型可能是也可能不是 Trivially Copyable 取决于在初始化器体中初始化类型的代码;对于这种类型,确定它是否是 Trivially Copyable 的唯一方法是对初始化程序进行人工检查。但是,如果您愿意收紧 可检测的要求,is_trivially_copyable将保证您的类型可以复制

答案 1 :(得分:2)

不,没有。

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

但即使你遇到了问题。

std::size_t可以是一个值,也可以是由其内存位置播种的const char*个哈希表的索引。这样的值可以安全地写出来,然后在下次执行程序时再次读取。

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

你最好的选择是使用你自己的Koenig标志功能的类型特征,声称对二进制序列化是安全的,有一些可覆盖的安全检查(如果类型不再是pod,标准布局,平凡可复制等,则会产生错误) )。

除此之外,您应该考虑使用归档系统。添加反射,使您可以读取/写入对象的状态。使聚合对象易于递归。

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

此代码根据Stream的类型分为读取器或编写器引擎,编码标头和(可选)*this某种类型的类型标志和长度。然后它将内容流入/流出。最后的额外内容会被自动丢弃。

对于二进制类型:

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。可以扩展非默认的可构造类型。可以编写Achive类型的非成员std

但这太过分了。简而言之,存档很困难,找到一个框架。