类型特征以识别可以是 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,不包括指针(但不包括数组)。类似于 StandardLayoutType
s 但没有指针。我不想将对象限制为 TrivialType
或 TriviallyCopyable
.
对不起,如果我不准确。我对数据表示知之甚少。
给定 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
因此你要确保你的对象是可简单复制这不是一个标准的概念,但它可以简洁地定义为:
- Every copy constructor is Trivial or deleted
- Every move constructor is Trivial or deleted
- Every copy assignment operator is Trivial or deleted
- Every move assignment operator is Trivial or deleted
- At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
- Trivial non-deleted destructor
断言对象至少存在一个 Trivial 初始化程序的精神归结为 Trivially Copyable 类型的这些要求,它是非静态成员,并且它的任何一个都是基础 类:
- 给定的 Trivial Initializer 是或表现为相应的默认初始化器
- 它没有虚拟方法
- 它没有 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_t
和 int
,它们在编译器 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& )
成员并调用它。没有它的它可以检测到 Read
和 Write
。 Non-default 可扩展的构造类型。 Non-member Achive
for std
types can be written.
但这太过分了。总之,归档难,找个框架。
是否有类型特征(或概念)来识别以下类型是安全的?
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
.
对不起,如果我不准确。我对数据表示知之甚少。
给定 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
因此你要确保你的对象是可简单复制这不是一个标准的概念,但它可以简洁地定义为:
- Every copy constructor is Trivial or deleted
- Every move constructor is Trivial or deleted
- Every copy assignment operator is Trivial or deleted
- Every move assignment operator is Trivial or deleted
- At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
- Trivial non-deleted destructor
断言对象至少存在一个 Trivial 初始化程序的精神归结为 Trivially Copyable 类型的这些要求,它是非静态成员,并且它的任何一个都是基础 类:
- 给定的 Trivial Initializer 是或表现为相应的默认初始化器
- 它没有虚拟方法
- 它没有 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_t
和 int
,它们在编译器 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& )
成员并调用它。没有它的它可以检测到 Read
和 Write
。 Non-default 可扩展的构造类型。 Non-member Achive
for std
types can be written.
但这太过分了。总之,归档难,找个框架。