“ size_t”应该包括哪个头?

根据 Cppreference.comsize_t定义在几个标头中,即

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

而且,因为 C + + 11,也在

<cstdlib>
<cwchar>

首先,我想知道为什么会出现这种情况。这不是与 干的原则相矛盾吗?

使用 size_t应该包括以上哪个标题? 这有什么关系吗?

97993 次浏览

你可以不用标题:

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在标准头 <cstddef>(18.2)中定义。ー尾注]

换句话说,标准要求:

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

还要注意,在全局名称空间和 std名称空间中进行这种 typedef声明完全没有问题,只要它与同一 Typedef-name的所有其他 typedef声明匹配(不匹配的声明会发出编译器错误)。

这是因为:

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

  • 7.1.3.3在给定的非类作用域中,可以使用 typedef说明符重新定义在该作用域中声明的任何类型的名称,以引用它已经引用的类型。


对于那些怀疑论者,他们认为这是在名称空间 std中增加了一个新的类型,而且这种行为是被标准明确禁止的,这就是 UB,这就是它的全部; 我不得不说,这种态度相当于忽视和否认对潜在问题的更深层次的理解。

该标准禁止在名称空间 std中添加新的声明和定义,因为这样做可能会使标准库变得一团糟,并导致用户的整条腿都断掉。对于标准作者来说,让用户专门做一些特定的事情并且禁止做任何其他的事情是更容易的,而不是禁止用户不应该做的每一件事情并且冒着错过一些重要的事情(和那条腿)的风险。他们在过去要求没有一个标准容器应该被实例化为一个不完整的类型时就这样做了,而事实上一些容器可以很好地做到这一点(见 标准图书馆员: 不完整类型的容器) :

最后,这一切看起来太模糊,理解太少; 标准化委员会认为除了说 STL 容器不应该与不完整的类型一起工作外别无选择。为了更好的措施,我们也将这个禁令应用到标准库的其他部分。

现在回想起来,这项技术已经被更好的理解了,这个决定看起来基本上是正确的。是的,在某些情况下,可以实现一些标准容器,以便用不完整的类型对它们进行实例化ーー但在其他情况下,这显然是困难的或不可能的。我们使用 std::vector进行的第一个测试碰巧是最简单的情况之一。

考虑到语言规则要求 std::size_t完全是 decltype(sizeof(int)),做 namespace std { using size_t = decltype(sizeof(int)); }是不会破坏任何东西的事情之一。

在 C + + 11之前,没有 decltype,因此没有办法在一个简单的语句中声明 sizeof结果的类型,而不涉及大量的模板。size_t在不同的目标体系结构上使用不同的类型作为别名,然而,仅仅为 sizeof的结果添加一个新的内置类型并不是一个优雅的解决方案,而且也没有标准的内置 typedef。因此,当时最可移植的解决方案是将 size_t类型别名放在一些特定的头和文档中。

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

假设我想要最小化导入的函数和类型,我会使用 cstddef,因为它不声明任何函数,只声明6种类型。其他的关注于特定的领域(字符串、时间、 IO) ,这些对你来说可能并不重要。

请注意,cstddef只保证定义 std::size_t,即在名称空间 std中定义 size_t,尽管 也在全局名称空间中提供这个名称(实际上是普通的 size_t)。

相反,stddef.h(也是 C 中可用的头)保证在全局名称空间中定义 size_t,而 也提供 std::size_t

所有标准库头文件都有相同的定义; 在您自己的代码中包含哪一个并不重要。在我的计算机上,我有以下 _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

事实上,几个头的概要(包括在 C + + 标准中)特别包括 size_t,而且进一步的头定义了 size_t类型(基于 C 标准,因为 <cX>头只是 ISO C <X.h>头,在没有指出去除 size_t的地方有注意到的变化)。

然而,C + + 标准 ABC1的定义是指 <cstddef>

  • 18.2类型,
  • 5.3.3尺寸,
  • (即18.2)及
  • (亦指18.2)。

因此,并且由于 <cstddef>只引入类型而不引入函数的事实,我将坚持使用这个头部来使 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 is not per se globally visible although functions with std::size_t arguments are.

    The implicitly declared global allocation and deallocation functions

    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 引用同一类型。

    尽管 std中出现的 size_t的多个定义对于 7.1.3/3来说是完全有效的,但是不允许在 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 不是模板专门化)。扩展 namespace std