是否有类型特征(或概念)来识别以下哪些类型是安全的?
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
。
很抱歉,如果我不准确的话。我对数据表示知之甚少。
答案 0 :(得分:5)
给定s
的1 st 参数,read
方法:
提取字符并将它们存储到字符数组的连续位置,其第一个元素由
s
指向
所以你真正的问题是:如果我通过向其地址写入一串字节来初始化一个对象,它是否有效?
这是Value Representation的概念。 Trivially Copyable类型的值表示如下:
复制存储中对象占用的字节足以生成具有相同值的另一个对象
因此,您希望确保您的对象 Trivially Copyable 这不是一个标准概念,但它可以简洁地定义为:
- 每copy constructor is Trivial或已删除
- 每move constructor is Trivial或已删除
- 每copy assignment operator is Trivial或已删除
- 每move assignment operator is Trivial或已删除
- 至少有一个复制构造函数,移动构造函数,复制赋值运算符或移动赋值运算符未被删除
- Trivial non-deleted destructor
对于对象存在至少一个 Trivial 初始值设定项的断言的精神归结为 Trivially Copyable 类型的这些要求,它是非静态成员及其任何基类:
至于 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_t
和int
,这两者在编译器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& )
成员并调用它。如果没有它,它可以检测到Read
和Write
。可以扩展非默认的可构造类型。可以编写Achive
类型的非成员std
。
但这太过分了。简而言之,存档很困难,找到一个框架。