【问题标题】:Replacing reinterpret_cast with better alternatives?用更好的替代品替换 reinterpret_cast?
【发布时间】:2016-01-24 19:02:22
【问题描述】:

我的项目中有几个地方使用reinterpret_cast 从流中读取/写入无符号整数。考虑以下函数:

size_t ReadSize(std::stringstream& stream) {
  char buf[sizeof(size_t)];
  stream.read(buf, sizeof(size_t));
  return *(reinterpret_cast<size_t*>(buf));
}

void WriteSize(std::stringstream& stream, size_t n) {
  stream.write(reinterpret_cast<char*>(&n), sizeof(size_t));
}

我开始对使用 reinterpret_cast 感到有些不自在,尽管我对它没有任何问题,所以我想知道,有没有更好的替代方法?假设我在流中只有 4 个字节应该代表这个整数。

static_cast 在这里也不适用,我想。有什么建议吗?

附:我目前不担心使用reinterpet_cast 可能引起的可移植性或其他特定于平台的问题。我正在为一台 Windows 机器写这篇文章。

【问题讨论】:

    标签: c++ reinterpret-cast


    【解决方案1】:

    虽然read(和write)函数被指定为采用char*,但您实际上不必传递一个字符数组,只需在@ 中将一个指向实际变量的指针987654325@(或write)改为:

    std::size_t size;
    if (stream.read(reinterpret_cast<char*>(&size), sizeof(size_t)))
        return size;
    return 0;  // Or something else on error
    

    在不相关的说明中,我建议您将流参数更改为 std::istream 引用,然后您可以将该函数用于任何输入流。

    【讨论】:

    • 谢谢,看起来好多了。但是在这种情况下有没有办法完全摆脱 reinterpret_cast ,或者如果我打算以这种方式从流中提取 int 是否有必要?
    • @InsomniaArray 可以通过使用type punningunion来摆脱类型转换。
    • @InsomniaArray:重新解释强制转换适用于 I/O 边界。
    • @KerrekSB 感谢您的安慰,我只是想确保我无能为力来减轻对它的需求。
    • @InsomniaArray:需要明确的是,这仅涵盖重新解释转换为指向 char 类型的指针。关键是 I/O 以字符(字节)的形式发生,您可以通过将对象视为字节序列并写入这些对象,从它们的字节对象表示中构建某些对象。 (但是,您不能将任意字节序列视为对象,就像您在问题中所做的那样。)
    【解决方案2】:

    所以你的代码的问题是,如果一个小端系统写入数据,而一个大端系统读取它。

    在这里,reinterpret_cast&lt;&gt; 将获取位图并应用它,而不考虑任何数据不兼容问题。

    优先顺序是:-

    • const_cast 仅用于删除/添加 const。
    • dynamic_cast 将预先创建的对象转换为兼容的基础/派生对象。
    • static_cast 使用编译时信息执行与 dynamic_cast 相同形式的转换
    • reinterpret_cast 将内存视为源和目标的联合。
    • C cast (void*)f; 使用reinterpret_cast / static_cast 之一转换类型。

    所以避免C cast。这是因为您无法真正判断编译器会选择什么。 const_cast / dynamic_cast 不要解决你的问题。

    所以最好的选择是reinterpret_cast

    【讨论】:

    • reinterpret_cast 最好理解为联合,但并不等同。它在适用性方面仍有一些限制,特别是在成员函数/数据指针方面。 C cast 也可以抛弃 const,而 IIRC 它也可以做 dynamic_cast。在某些情况下。
    • 我确实意识到字节序存在问题,因此我指定这些问题目前不是我关心的问题。感谢您的洞察力,但是,非常感谢。
    • 我很困惑为什么 static_cast 在列表中排名靠后。我会优先将它放在 dynamic_cast 之上......虽然 const_cast 有它的用途,但我通常发现它的用途是一种代码味道,因为它可能导致未定义的行为。
    【解决方案3】:

    由于使用的是字符串流,所以可以直接访问它用作缓冲区的字符串:

    ReadSize(std::stringstream& stream) {
      return *(reinterpret_cast<size_t*>(stream.str().c_str()));
    }
    

    这样可以节省一些复制。

    无论如何,这不是你的问题。只有当您的流提供的数据与您的机器使用的字节序相同时,您的代码才会按预期工作。您可能更喜欢显式处理字节序:

    ReadSize(std::istream& stream) {
      char buf[sizeof(size_t)];
      stream.read(buf, sizeof(size_t));
      return (static_case<size_t>(buf[0]) << 24) | 
             (static_case<size_t>(buf[1]) << 16) |
             (static_case<size_t>(buf[2]) << 9) |
             (static_case<size_t>(buf[3]));
    }
    

    顺便说一句,你也摆脱了reinterpret_cast&lt;&gt;

    【讨论】:

    • 这是对 size_t 大小的假设。此外,您的第一个函数可能无法正常工作,具体取决于返回值类型。
    • @NeilKirk 是的,但从流中读取二进制数据通常涉及已定义的流格式,因此流中已经存在大小类型,读取它的代码应该反映这一点。返回值类型 BTW 从问题中显而易见,尽管示例代码缺少它(我的也是如此):size_t
    • 在第一个函数中,如果 buf 有 4 个字节但 size_t 有 8 个字节,则会出现未定义的行为。在编程中也没有什么是显而易见的。如果它返回const size_t&amp;,那么它将不起作用。
    • 如果返回类型是const size_t&amp;,那么问题中的代码就不能正常工作了,就像我的一样。如果buf 有 4 个字节,而size_t 有 8 个字节,则问题中的原始代码同样会失败,返回随机位。
    • 您对返回类型是正确的——我很抱歉。关于第二点,这也是正确的,但我只是担心 OP 没有考虑到失败的可能性。
    【解决方案4】:

    您的代码对size_t 的大小进行了假设,即使在 Windows 上也不总是 4 个字节。如果将 4 个字节写入流中,并且您尝试使用编译的代码(其中 sizeof(size_t) 为 8)读取它会发生什么?

    您可以使用以下函数安全且可移植地(也可能)将字节转换为整数。当然,它假定提供的数组足够大。

    template<class T>
    T ComposeBytes(const char *bytes)
    {
        T x = 0;
        for (size_t i = 0; i < sizeof(T); i++)
        {
            x |= (static_cast<unsigned char>(bytes[i]) << (i * CHAR_BIT));
        }
        return x;
    }
    

    编辑:修复了 char 签名的问题。

    【讨论】:

    • 他的代码在哪里对size_t 做出了假设?他到处使用 sizeof(size_t)。
    • @cdonat 流中有什么?它来自哪里?
    • 看他的第二个函数。请求者写道,他的代码确实有效,但他对此感到不舒服。所以他所做的任何假设都是成立的。
    • @cdonat 他说他不关心跨平台,只关心 Windows。他并没有只指定 32 位 Windows。这个问题可能会在实际程序中导致真正的错误,花费真正的美元,所以重要的是要提及它。
    • @NeilKirk 我不认为size_t 的长度可能是 8 个字节。感谢您提出这一点。我是否正确假设 size_t 在为 64 位窗口编译时将是 8 个字节?我想我最好使用 uint_32t 而不是 size_t 之类的东西以获得更好的可移植性。
    猜你喜欢
    • 2021-06-30
    • 1970-01-01
    • 2013-09-05
    • 2012-04-02
    • 1970-01-01
    • 1970-01-01
    • 2019-08-27
    • 1970-01-01
    • 2017-08-03
    相关资源
    最近更新 更多