【问题标题】:Which header should I include for `size_t`?我应该为“size_t”包含哪个标题?
【发布时间】:2016-08-04 07:45:10
【问题描述】:

根据cppreference.comsize_t定义在几个header中,即

<cstddef>
<cstdio>
<cstring>
<ctime>

而且,从 C++11 开始,也在

<cstdlib>
<cwchar> 

首先,我想知道为什么会这样。这不是与DRY 原则相矛盾吗?

在使用size_t 时,我应该包含上述哪个标头?有关系吗?

【问题讨论】:

  • 打开对应的头文件,找到定义。
  • @i486 - 这是编写易碎的不可移植代码的好方法!
  • @PanagiotisKanavos C 标头是 C++ 标准库的一部分,并且可能不会在您所谓的任何“真正的 C++”标头中重复。你到底想说什么?
  • 我一直用&lt;cstddef&gt; 代替std::size_t
  • @PanagiotisKanavos 当然,这通常是个好建议,但在这种情况下,它似乎无关紧要 - 因为没有 C++ 替代 std::size_t,并且 OP 并不提倡使用遗留 C 函数,只是观察关于他们共享 typedef 的引用。我怀疑任何阅读此主题的人都会因此而被误导使用遗留类型/函数,但如果你想确保他们没有,那么就足够公平了!

标签: c++ typedef


【解决方案1】:

假设我想最小化我正在导入的函数和类型,我会选择cstddef,因为它没有声明任何函数,只声明了 6 种类型。其他人专注于对您来说可能无关紧要的特定领域(字符串、时间、IO)。

注意cstddef 仅保证定义std::size_t,即在命名空间std 中定义size_t,尽管它可能 在全局命名空间中也提供此名称(实际上,纯size_t)。

相比之下,stddef.h(也是 C 中可用的标头)保证在全局命名空间中定义 size_t可能还提供std::size_t

【讨论】:

  • 是否可以保证来自cstddefsize_t 是相同的并且永远与其他人相同?似乎应该有一个通用头文件,其通用定义如size_t...
  • @SnakeDoc 就像魔术一样,这里的另一个答案已经通过“内部”标题准确地观察到了这种情况。
  • @SnakeDoc 是的,那个标题是cstddef
  • @SnakeDoc,谁说他们自己定义的?所有标准都说它将在包含这些标头后定义,并没有说它们都必须重新定义它。它们都可以包含&lt;cstddef&gt;,或者它们都可以包含一些只定义size_t的内部标头。
  • 答案中的csttddef 是错字吗?也许cstddef 是什么意思?
【解决方案2】:

事实上,几个头文件的概要(包含在 C++ 标准中)特别包括 size_t 以及进一步的头文件定义了 size_t 类型(基于 C 标准,因为 &lt;cX&gt; 头文件只是 ISO C @ 987654326@ 标头有注明的更改,其中未指示删除 size_t)。

C++ 标准然而,引用&lt;cstddef&gt; 来定义std::size_t

  • 18.2 类型
  • 5.3.3 Sizeof
  • 3.7.4.2 释放函数(指18.2)和
  • 3.7.4.1 分配函数(也参考18.2)。

因此,由于 &lt;cstddef&gt; 仅引入类型而没有引入函数,我会坚持使用此标头以使 std::size_t 可用。


注意几点:

  1. std::size_t 的类型可以使用 decltype 获得,不包括标题

    如果您打算在代码中引入 typedef(即,因为您编写容器并希望提供 size_type typedef),您可以使用全局 sizeofsizeof...alignof 运算符定义您的类型而不包含任何标题,因为这些运算符根据标准定义返回std::size_t,您可以在它们上使用decltype

    using size_type = decltype(alignof(char));
    
  2. std::size_t 本身并不是全局可见的,尽管带有 std::size_t 参数的函数是全局可见的。

    隐式声明的全局分配和释放函数

    void* operator new(std::size_t);
    void* operator new[](std::size_t);
    void operator delete(void*);
    void operator delete[](void*);
    

    不要引入size_tstdstd::size_t

    引用 stdstd::size_t 是不正确的,除非已通过包含适当的标头来声明名称。

  3. 用户不能重新定义std::size_t,尽管在同一个命名空间中可能有多个 typedef 引用同一个类型。

    虽然根据7.1.3 / 3size_tstd 中出现多个定义是完全有效的,但不允许在namespace std 中添加任何声明。 17.6.4.2.1 / 1

    除非另有说明,否则如果 C++ 程序将声明或定义添加到命名空间 std 或命名空间 std 内的命名空间,则其行为未定义。

    size_t 添加适当的 typedef 到命名空间不会违反 7.1.3,但它确实违反了 17.6.4.2.1 并导致未定义的行为。

    澄清:尽量不要误解 7.1.3 并且不要在std 中添加声明或定义(除了一些 typedef 不是模板的模板特化情况专业化)。 Extending the namespace std

【讨论】:

  • 您错过了重复 typedef 不会引入新类型的事实。它只是添加了一个重复的 typedef,这是完全有效的。
  • @MaximEgorushkin:我并不声称将重新定义的 typedef 添加到 std 是无效的,因为重复的 typedef 是非法的。我声明这是非法的,因为您根本不能向 namespace std 添加定义 - 无论它们是否合法。
  • 鉴于我们从所有这些标准报价中所知道的一切,什么可能会破坏?
  • @MaximEgorushkin:随便。这就是未定义行为的意义所在,不是吗?它可能工作的点,甚至它不会在任何任意编译器上都不会中断的点,都不会使程序的行为根据标准定义。或者正如“fredoverflow”所说的那样 here:“C++ 标准拥有唯一的投票权,句号。”
  • 我希望您能运用批判性思维。什么可能会破坏?
【解决方案3】:

所有标准库头文件都有相同的定义;您在自己的代码中包含哪一个并不重要。在我的电脑上,我在_stddef.h 中有以下声明。您列出的每个文件都包含此文件。

/*
   Define the size_t type in the std namespace if in C++ or globally if in C.
   If we're in C++, make the _SIZE_T macro expand to std::size_t
*/

#if !defined(_SIZE_T) && !defined(_SIZE_T_DEFINED)
#  define _SIZE_T_DEFINED
#if defined(_WIN64)
   typedef unsigned __int64 size_t;
#else
   typedef unsigned int size_t;
#endif
#  if defined(__cplusplus)
#    define _SIZE_T std::size_t
#  else
#    define _SIZE_T size_t
#  endif
#endif

【讨论】:

  • 不确定,但我认为编译时间很重要,不是吗?
  • @tobi303 不适用于这个特定问题。是的,您可以添加比必要更大的标头,但是您已经在 C++ 项目中添加了 C 标头。为什么首先需要size_t
  • 使用 OS 宏嗅探来定义 size_t 不是一个好主意。您可以将其更便携地定义为using size_t = decltype( sizeof( 42 ) )。但是没有必要,因为&lt;stddef.h&gt; 的成本几乎为零。
【解决方案4】:

你可以不用标题:

using size_t = decltype(sizeof(int));
using size_t = decltype(sizeof 1); //  The shortest is my favourite.
using size_t = decltype(sizeof "anything");

这是因为 C++ 标准要求:

sizeofsizeof... 的结果是std::size_t 类型的常量。 [注:std::size_t 在标准头文件&lt;cstddef&gt; (18.2) 中定义。 ——尾注]

换句话说,标准要求:

static_assert(std::is_same<decltype(sizeof(int)), std::size_t>::value,
              "This never fails.");

还请注意,在全局和 std 命名空间中创建此 typedef 声明是完全可以的,只要它与相同 typedef-name 的所有其他 typedef 声明匹配em>(对不匹配的声明发出编译器错误)。

这是因为:

  • §7.1.3.1 typedef-name 不会像类声明 (9.1) 或枚举声明那样引入新类型。

  • §7.1.3.3 在给定的非类范围内,typedef 说明符可用于重新定义在该范围内声明的任何类型的名称,以引用它已经引用的类型。


怀疑论者说这构成了在命名空间std 中添加新类型,而这种行为被标准明确禁止,这就是 UB,仅此而已;不得不说,这种态度等于无视和否认对根本问题的更深理解。

标准禁止在命名空间std 中添加新的声明和定义,因为这样做用户可能会弄​​乱标准库并打断他的整条腿。对于标准编写者来说,让用户专注于一些特定的事情并禁止做任何其他事情会更容易,而不是禁止用户不应该做的每一件事,并冒着错过重要事情(和那条腿)的风险。他们过去在要求任何标准容器都不应使用不完整类型实例化时这样做,而实际上某些容器可以做到(参见The Standard Librarian: Containers of Incomplete Types by Matthew H. Austern):

... 到头来,这一切都显得太模糊,太难理解了;标准化委员会认为除了说 STL 容器不应该与不完整的类型一起使用之外,别无选择。为了更好地衡量,我们也将该禁令应用于标准库的其余部分。

... 回想起来,既然对技术有了更好的理解,那个决定似乎仍然基本上是正确的。是的,在某些情况下,可以实现一些标准容器,以便可以使用不完整的类型对其进行实例化——但也很清楚,在其他情况下,这将是困难的或不可能的。我们使用std::vector 尝试的第一个测试碰巧是一个简单的案例,这主要是偶然的。

鉴于语言规则要求 std::size_tdecltype(sizeof(int)) 完全一致,因此执行 namespace std { using size_t = decltype(sizeof(int)); } 是不会破坏任何东西的事情之一。

在 C++11 之前,没有 decltype,因此无法在一个简单的语句中声明 sizeof 的类型,而无需涉及大量模板。 size_t 为不同目标架构上的不同类型别名,然而,仅仅为sizeof 的结果添加新的内置类型并不是一个优雅的解决方案,并且没有标准的内置类型定义。因此,当时最便携的解决方案是将size_t 类型别名放在某个特定的标题中并记录下来。

在 C++11 中,现在有一种方法可以将标准的确切要求写成一个简单的声明。

【讨论】:

  • @Sean 你写的没有任何意义。
  • @PanagiotisKanavos 你在说什么?此size_t 未签名。
  • @MaximEgorushkin 其中一半人不理解这段代码......它工作得很好。但是,我不喜欢这种方式:imo,最好包含一个标题并让标准定义它。
  • 伙计们,在你投反对票完全正确的答案之前,至少要学习有效的语言。
  • Tom 说,“有 6 个标准库头文件定义了相同的东西!这太疯狂了!我们需要一个且只有一个 size_t 的定义!”一分钟后,Mary 说:“天啊!标准库头文件中有 7 个size_t 的定义,Tom 正在编辑一个项目头文件!第三方库中可能还有更多!” xkcd.com/927
猜你喜欢
  • 2011-07-02
  • 1970-01-01
  • 2011-01-18
  • 2021-02-28
  • 1970-01-01
  • 2010-09-19
  • 1970-01-01
  • 2010-10-30
  • 2015-07-28
相关资源
最近更新 更多