web-dev-qa-db-fra.com

Quel en-tête dois-je inclure pour `size_t`?

Selon cppreference.comsize_t est défini dans plusieurs en-têtes, à savoir

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

Et, depuis C++ 11, également dans

<cstdlib>
<cwchar> 

Tout d'abord, je me demande pourquoi c'est le cas. Cela n’est-il pas en contradiction avec le principe SEC ? Cependant, ma question est la suivante:

Lequel des en-têtes ci-dessus dois-je inclure pour utiliser size_t? Est-ce important?

En supposant que je veuille minimiser les fonctions et les types que j’importais, j’allais utiliser cstddef, car il ne déclare aucune fonction et ne déclare que 6 types. Les autres se concentrent sur des domaines particuliers (chaînes de caractères, heure, IO) qui ne vous importent peut-être pas.

Notez que cstddef ne garantit que la définition de std::size_t, c’est-à-dire définir size_t dans l’espace de nommage std, bien que puisse fournir également ce nom dans l’espace de nommage global (dans les faits, plain size_t).

En revanche, stddef.h (qui est aussi un en-tête disponible en C) garantit de définir size_t dans l’espace de noms global, et peut fournit également std::size_t.

70
Sean

En fait, le synopsis (inclus dans la norme C++) de plusieurs en-têtes inclut spécifiquement size_t, Ainsi que d'autres en-têtes définissant le type size_t (Basé sur la norme C en tant que <cX> les en-têtes ne sont que des en-têtes ISO C <X.h> avec les modifications notées lorsque la suppression de size_t n'est pas indiquée).

Le standard C++ cependant fait référence à <cstddef> Pour la définition de std::size_t

  • dans 18.2 Types,
  • dans 5.3.3 Sizeof,
  • dans 3.7.4.2 Fonctions de désallocation (qui fait référence à 18.2) et
  • in 3.7.4.1 Fonctions d’allocation (fait également référence à 18.2).

Par conséquent et à cause du fait que <cstddef> N'introduit que des types et pas de fonctions, je m'en tiendrai à cet en-tête pour rendre std::size_t Disponible.


Notez quelques points:

  1. Le type de std::size_t Peut être obtenu en utilisant decltype sans inclure d'en-tête

    Si vous envisagez quand même d'introduire un typedef dans votre code (c'est-à-dire parce que vous écrivez un conteneur et que vous voulez fournir un typedef size_type), Vous pouvez utiliser le global sizeof, sizeof... ou alignof opérateurs pour définir votre type sans inclure aucun en-tête, car les opérateursose renvoient std::size_t par définition standard et vous pouvez utiliser decltype:

    using size_type = decltype(alignof(char));
    
  2. std::size_t N'est pas globalement visible, bien que les fonctions avec les arguments std::size_t Le soient.

    Les fonctions d'allocation globale et de désallocation implicitement déclarées

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

    n'introduisez PAS size_t, std ou std::size_t et

    faire référence à std ou std::size_t est mal formé à moins que le nom n'ait été déclaré en incluant l'en-tête approprié.

  3. L'utilisateur ne peut pas redéfinir std::size_t Bien qu'il soit possible d'avoir plusieurs typedefs faisant référence au même type dans le même espace de noms.

    Bien que l'occurrence de plusieurs définitions de size_t Au sein de std soit parfaitement valide selon 7.1.3/3, il n'est pas permis d'ajouter de déclaration à namespace std Conformément à 17.6.4.2.1/1:

    Le comportement d'un programme C++ n'est pas défini s'il ajoute des déclarations ou des définitions à namespace std ou à un espace de nommage situé dans namespace std, sauf indication contraire.

    L'ajout d'un typedef approprié pour size_t À l'espace de noms ne viole pas 7.1.3 mais viole 17.6.4.2.1 et conduit à comportement indéfini.

    Clarification: Essayez de ne pas mal interpréter 7.1.3 et n’ajoutez pas de déclarations ou de définitions à std (sauf quelques cas de spécialisation de modèles où typedef n'est pas une spécialisation de modèle). Étendre le namespace std

40
Pixelchemist

Tous les fichiers d’en-tête de bibliothèque standard ont la même définition; peu importe celui que vous incluez dans votre propre code. Sur mon ordinateur, j'ai la déclaration suivante en _stddef.h. Ce fichier est inclus par tous les fichiers que vous avez listés.

/*
   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
9
Ville-Valtteri

Vous pouvez faire sans en-tête:

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

En effet, la norme C++ requiert:

Le résultat de sizeof et de sizeof... Est une constante de type std::size_t. [Remarque: std::size_t Est défini dans l'en-tête standard <cstddef> (18.2). - note de fin]

En d'autres termes, la norme requiert:

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

Notez également qu’il est parfaitement correct de faire cette déclaration typedef dans les espaces de noms global et std, à condition qu’elle corresponde à toutes les autres déclarations typedef du même typedef-name (une erreur du compilateur est générée sur les déclarations non concordantes).

Ceci est dû au fait:

  • §7.1.3.1 Un typedef-name n'introduit pas un nouveau type comme le ferait une déclaration de classe (9.1) ou enum.

  • §7.1.3.3 Dans une portée non-classe donnée, un spécificateur typedef peut être utilisé pour redéfinir le nom de tout type déclaré dans cette portée afin de faire référence au type auquel il fait déjà référence.


Aux sceptiques qui disent que cela constitue l'ajout d'un nouveau type dans l'espace de noms std, et qu'un tel acte est explicitement interdit par la norme, et c'est UB et c'est tout ce qui s'y trouve; Je dois dire que cette attitude revient à ignorer et à nier une compréhension plus profonde des problèmes sous-jacents.

La norme interdit d’ajouter de nouvelles déclarations et définitions dans l’espace de noms std, ce qui risquerait de désorganiser la bibliothèque standard et d’abattre toute sa jambe. Pour les rédacteurs standard, il était plus facile de laisser l'utilisateur spécialiser quelques choses spécifiques et interdire de faire autre chose pour faire bonne mesure, plutôt que d'interdire tout ce que l'utilisateur ne devrait pas faire et risquer de rater quelque chose d'important (et cette jambe). Ils l'avaient déjà fait dans le passé en exigeant qu'aucun conteneur standard ne soit instancié avec un type incomplet, alors que certains conteneurs pourraient très bien le faire (voir Le bibliothécaire standard: Les conteneurs de types incomplets de Matthew H. Austern ):

… Au final, tout cela semblait trop sombre et trop mal compris; le comité de normalisation ne pensait pas qu'il y avait d'autre choix que de dire que les conteneurs STL ne sont pas supposés fonctionner avec des types incomplets. Pour faire bonne mesure, nous avons également appliqué cette interdiction au reste de la bibliothèque standard.

... Rétrospectivement, maintenant que la technologie est mieux comprise, cette décision semble toujours fondamentalement juste. Oui, dans certains cas, il est possible d'implémenter certains conteneurs standard afin de pouvoir les instancier avec des types incomplets - mais il est également clair que dans d'autres cas, cela serait difficile, voire impossible. C'était principalement le hasard que le premier test que nous avons essayé, en utilisant std::vector, Soit l'un des cas les plus faciles.

Etant donné que les règles de langage exigent que std::size_t Soit exactement decltype(sizeof(int)), faire namespace std { using size_t = decltype(sizeof(int)); } est une de ces choses qui ne casse rien.

Avant C++ 11, il n'y avait pas de decltype et donc aucun moyen de déclarer le type de résultat de sizeof en une seule déclaration sans impliquer un grand nombre de modèles. size_t Alias ​​différents types sur différentes architectures cibles. Toutefois, ce ne serait pas une solution élégante d'ajouter un nouveau type intégré uniquement pour le résultat de sizeof, et il n'existe pas de norme intégrée. en typedefs. Par conséquent, la solution la plus portable à l'époque était de mettre un alias de type size_t Dans un en-tête spécifique et de le documenter.

En C++ 11, il existe maintenant un moyen d'écrire cette exigence exacte de la norme sous la forme d'une simple déclaration.

4
Maxim Egorushkin