C++中相互转换不同签名类型的最佳方法是什么



通信对等端发送了一个uint64_t数据字段,它携带了一个订单ID,我需要将其存储到不支持无符号整数类型的Postgresql-11数据库中。虽然一个真实的数据可能超过2^63,但我认为如果我仔细地进行一些转换,Postgresql11中的INT8文件可以容纳它。

假设有:

uint64_t order_id = 123; // received
int64_t  to_db;          // to be writed into db

我计划使用以下方法之一将uint64_t值强制转换为int64_t:

  1. to_db = order_id;//直接赋值
  2. to_db = (int64_t)order_id;//c型铸造
  3. to_db = static_cast<int64_t>(order_id);
  4. to_db = *reinterpret_cast<const int64_t*>( &order_id );

当我需要从数据库加载它时,我可以进行反向铸造。

我知道它们都有效,我只是感兴趣的是哪一个最完美地满足C++标准。

换句话说,哪种方法在任何64位平台和任何编译器中都能工作?

取决于编译和运行它的位置。。。在没有C++20支持的情况下不能完全移植的任何一种。

最安全的方法是通过改变值的范围来进行转换,类似

int64_t to_db = (order_id > (uint64_t)LLONG_MAX) 
? int64_t(order_id - (uint64_t)LLONG_MAX - 1) 
: int64_t(order_id ) - LLONG_MIN;
uint64_t from_db = (to_db < 0) 
? to_db + LLONG_MIN
: uint64_t(to_db) +  (uint64_t)LLONG_MAX  + 1;

如果order_id大于(2^63-1(,则order_id - (uint64_t)LLONG_MAX - 1产生非负值。如果不是,那么强制转换为带符号是定义良好的,减法可以确保值被转移到负范围。

在反向转换过程中,to_db + LLONG_MIN将值置于[0,ULLONG_MAX]范围内。

在阅读方面则相反。当将无符号值转换为有符号值时,您使用的数据库平台或编译器可能会对无符号值的二进制表示进行糟糕的处理,更不用说存在不同格式的有符号值了。

出于同样的原因,平台间协议通常涉及使用字符串格式或"字符串";最小比特值";用于将浮点值表示为整数(即编码的不动点(。

我会选择memcpy。它避免了(参见注释(未定义的行为,通常编译器会优化任何字节拷贝:

int64_t uint64_t_to_int64_t(uint64_t u)
{
int64_t i;
memcpy(&i, &u, sizeof(int64_t));
return i;
}
order_id = uint64_t_to_int64_t(to_db);

GCC与-O2生成了uint64_t_to_int64_t:的最佳组合

mov rax, rdi
ret

现场演示:https://godbolt.org/z/Gbvhzh

只要值在范围内,所有四个方法都将始终有效。第一个会在许多编译器上生成警告,因此可能不应该使用。第二个更像是一个C习惯用法,而不是C++习惯用法,但在C++中被广泛使用。最后一个是丑陋的,依赖于标准中的细微细节,不应该使用。

此函数似乎没有UB

int64_t fromUnsignedTwosComplement(uint64_t u)
{
if (u <= std::numeric_limits<int64_t>::max()) return static_cast<int64_t>(u);
else return -static_cast<int64_t>(-u);
}

在优化的情况下,它减少为无操作。

另一个方向的转换是直接转换为uint64_t。它总是定义明确的。

相关内容

  • 没有找到相关文章

最新更新