Qual cabeçalho devo incluir para `size_t`?

95

De acordo com cppreference.com size_t é definido em vários cabeçalhos, nomeadamente

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

E, desde C ++ 11, também em

<cstdlib>
<cwchar> 

Em primeiro lugar, me pergunto por que isso acontece. Isso não está em contradição com o princípio DRY ? No entanto, minha pergunta é:

Qual dos cabeçalhos acima devo incluir para usar size_t? Isso tem alguma importância?

idclev 463035818
fonte
1
Abra os arquivos de cabeçalho correspondentes e encontre a definição.
i486,
33
@ i486 - É uma ótima maneira de escrever código frágil e não portátil!
Sean
3
@PanagiotisKanavos cabeçalhos C que fazem parte da biblioteca padrão C ++ e provavelmente não estão duplicados em nenhum dos seus supostos cabeçalhos 'verdadeiros C ++'. Qual foi o seu ponto, exatamente?
sublinhado_d
14
Eu sempre usei <cstddef>parastd::size_t
Boiethios
4
@PanagiotisKanavos Claro, geralmente esse é um bom conselho, mas neste caso não parece relevante - já que não há substituto para C ++ std::size_te o OP não estava defendendo o uso de funções C legadas, apenas observando a citação sobre eles compartilharem o typedef. Duvido que alguém lendo este tópico seja induzido a usar tipos / funções legados por causa disso, mas se você quiser ter certeza de que não o fazem, então é justo!
sublinhado_d

Respostas:

90

Supondo que eu quisesse minimizar as funções e os tipos que estava importando, eu usaria, cstddefpois ele não declara nenhuma função e declara apenas 6 tipos. Os outros focam em domínios específicos (strings, tempo, IO) que podem não importar para você.

Observe que cstddefapenas garante definir std::size_t, ou seja, definir size_tno namespace std, embora possa fornecer esse nome também no namespace global (efetivamente, simples size_t).

Em contraste, stddef.h(que também é um cabeçalho disponível em C) garante a definição size_tno namespace global e também pode fornecer std::size_t.

Sean
fonte
3
Existe alguma garantia de que size_tde cstddefé o mesmo e sempre será o mesmo que os outros? Parece que deveria haver um arquivo de cabeçalho comum com definições comuns como size_t...
SnakeDoc
1
@SnakeDoc e como que por mágica, outra resposta aqui já observou exatamente isso acontecendo, através de um cabeçalho 'interno'.
underscore_d
5
@SnakeDoc Sim, e esse cabeçalho é cstddef.
user253751
2
@SnakeDoc, quem disse que eles definem os seus próprios? Tudo o que o padrão diz é que será definido após incluir esses cabeçalhos, não diz que todos eles precisam redefini-lo. Eles podem incluir <cstddef>ou podem incluir algum cabeçalho interno que apenas define size_t.
Jonathan Wakely,
1
A csttddefresposta é um erro de digitação? Talvez cstddefse queira dizer?
Erik Sjölund
46

Na verdade, a sinopse (incluída no padrão C ++) de vários cabeçalhos inclui especificamente size_t, bem como outros cabeçalhos definem o tipo size_t(com base no padrão C, pois os <cX>cabeçalhos são apenas <X.h>cabeçalhos ISO C com alterações observadas onde a remoção de size_tnão é indicada).

O padrão C ++, no entanto, refere-se à <cstddef>definição destd::size_t

  • em 18.2 Tipos ,
  • em 5.3.3 Sizeof ,
  • em 3.7.4.2 Funções de desalocação (que se refere a 18.2) e
  • em 3.7.4.1 Funções de alocação (também se refere a 18.2).

Portanto, e devido ao fato de que <cstddef>introduz apenas tipos e nenhuma função, eu ficaria com este cabeçalho para std::size_tdisponibilizá-lo.


Observe algumas coisas:

  1. O tipo de std::size_tpode ser obtido decltypesem incluir um cabeçalho

    Se você está planejando para introduzir um typedef em seu código de qualquer maneira (ou seja, porque você escreve um recipiente e deseja fornecer um size_typetypedef) você pode usar os globais sizeof, sizeof...ou alignofoperadores para definir o seu tipo sem incluir quaisquer cabeçalhos em tudo desde theose operadores retornar std::size_tper definição padrão e você pode usar decltypeneles:

    using size_type = decltype(alignof(char));
  2. std::size_tnão é per se globalmente visível, embora funções com std::size_targumentos sejam.

    As funções de alocação e desalocação globais implicitamente declaradas

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

    NÃO apresente size_t, stdou std::size_te

    referindo-se a stdou std::size_testá mal formado, a menos que o nome tenha sido declarado incluindo o cabeçalho apropriado.

  3. O usuário não pode redefinir, std::size_tembora seja possível ter vários typedefs referentes ao mesmo tipo no mesmo namespace.

    Embora a ocorrência de múltiplas definições de size_tdentro stdseja perfeitamente válida conforme 7.1.3 / 3 , não é permitido adicionar quaisquer declarações namespace stdconforme 17.6.4.2.1 / 1 :

    O comportamento de um programa C ++ é indefinido se ele adiciona declarações ou definições ao namespace std ou a um namespace dentro do namespace std, a menos que especificado de outra forma.

    Adicionar um typedef adequado para size_tao namespace não viola 7.1.3, mas viola 17.6.4.2.1 e leva a um comportamento indefinido.

    Esclarecimento: Tente não interpretar mal 7.1.3 e não adicione declarações ou definições std(exceto alguns casos de especialização de template onde um typedef não é uma especialização de template). Estendendo onamespace std

Pixelchemist
fonte
1
Você perde o fato de que um typedef duplicado não introduz um novo tipo. Ele simplesmente adiciona um typedef duplicado, o que é perfeitamente válido.
Maxim Egorushkin
@MaximEgorushkin: Não afirmo que adicionar um typedef de redefinição a stdseja inválido porque typedefs duplicados são ilegais. Afirmo que é ilegal porque você simplesmente não pode adicionar definições a namespace std- não importa se elas seriam legais.
Pixelchemist
O que poderia quebrar, dado tudo o que sabemos de todas essas citações padrão?
Maxim Egorushkin
12
@MaximEgorushkin: Qualquer coisa. É disso que se trata o comportamento indefinido, não é? O ponto que pode trabalhar ou até o ponto que se não quebrar em qualquer compilador arbitrários não faz o comportamento do programa definido de acordo com o padrão. Ou, como 'fredoverflow' colocou muito bem aqui : "O padrão C ++ tem o único voto, ponto final."
Pixelchemist
Eu gostaria que você usasse seu pensamento crítico. O que poderia quebrar?
Maxim Egorushkin
9

Todos os arquivos de cabeçalho de biblioteca padrão têm a mesma definição; não importa qual você incluir em seu próprio código. No meu computador, tenho a seguinte declaração em _stddef.h. Este arquivo está incluído em todos os arquivos listados.

/*
   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
vll
fonte
2
não tenho certeza, mas acho que importa para o tempo de compilação, não?
idclev 463035818
@ tobi303 não para esta questão específica. Sim, você pode adicionar um cabeçalho maior do que o necessário, mas então você adicionou um cabeçalho C em um projeto C ++. Por que você precisa size_tem primeiro lugar?
Panagiotis Kanavos
Não é uma boa ideia usar o farejamento de macro do sistema operacional para definir size_t. Você pode defini-lo de forma mais portátil como using size_t = decltype( sizeof( 42 ) ). Mas não há necessidade, pois <stddef.h>tem custo quase zero.
Saúde e hth. - Alf
4

Você poderia fazer sem um cabeçalho:

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

Isso ocorre porque o padrão C ++ requer:

O resultado de sizeofe sizeof...é uma constante do tipo std::size_t. [Nota: std::size_té definido no cabeçalho padrão <cstddef>(18.2). - nota final]

Em outras palavras, o padrão requer:

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

Observe também que é perfeitamente correto fazer essa typedefdeclaração no global e no stdnamespace, desde que corresponda a todas as outras typedefdeclarações do mesmo nome de typedef (um erro do compilador é emitido em declarações não correspondentes).

Isto é porque:

  • §7.1.3.1 Um typedef-name não introduz um novo tipo da mesma forma que uma declaração de classe (9.1) ou declaração enum o faz.

  • §7.1.3.3 Em um determinado escopo de não classe, um typedefespecificador pode ser usado para redefinir o nome de qualquer tipo declarado naquele escopo para se referir ao tipo ao qual ele já se refere.


Para os céticos, dizendo que isso constitui uma adição de um novo tipo ao namespace std, e tal ato é explicitamente proibido pelo padrão, e isso é UB e isso é tudo; Devo dizer que essa atitude equivale a ignorar e negar uma compreensão mais profunda das questões subjacentes.

O padrão proíbe a adição de novas declarações e definições no namespace stdporque, ao fazer isso, o usuário pode bagunçar a biblioteca padrão e atirar em toda a sua perna. Para os escritores padrão, era mais fácil deixar o usuário se especializar em algumas coisas específicas e banir qualquer outra coisa para uma boa medida, ao invés de banir cada coisa que o usuário não deveria fazer e correr o risco de perder algo importante (e aquela perna). Eles fizeram isso no passado ao exigir que nenhum contêiner padrão seja instanciado com um tipo incompleto, enquanto na verdade alguns contêineres bem poderiam fazer (consulte The Standard Librarian: Containers of Incomplete Types de Matthew H. Austern ):

... No final, tudo parecia muito obscuro e mal compreendido; o comitê de padronização não achou que houvesse outra escolha exceto dizer que os contêineres STL não deveriam funcionar com tipos incompletos. Por precaução, aplicamos essa proibição ao resto da biblioteca padrão também.

... Em retrospecto, agora que a tecnologia foi melhor compreendida, essa decisão ainda parece basicamente correta. Sim, em alguns casos é possível implementar alguns dos contêineres padrão para que eles possam ser instanciados com tipos incompletos - mas também está claro que em outros casos isso seria difícil ou impossível. Foi principalmente por acaso que o primeiro teste que tentamos, usando std::vector, foi um dos casos fáceis.

Dado que as regras da linguagem exigem std::size_tser exatas decltype(sizeof(int)), fazer namespace std { using size_t = decltype(sizeof(int)); }é uma daquelas coisas que não quebra nada.

Antes do C ++ 11, não havia decltypee, portanto, nenhuma maneira de declarar o tipo de sizeofresultado em uma instrução simples sem envolver uma grande quantidade de modelos. size_taliases diferentes tipos em diferentes arquiteturas de destino, no entanto, não seria uma solução elegante adicionar um novo tipo integrado apenas pelo resultado de sizeof, e não há typedefs integrados padrão. Conseqüentemente, a solução mais portátil na época era colocar size_talias de tipo em algum cabeçalho específico e documentá-lo.

No C ++ 11 agora existe uma maneira de escrever esse requisito exato do padrão como uma declaração simples.

Maxim Egorushkin
fonte
6
@Sean O que você escreveu não faz nenhum sentido.
Maxim Egorushkin
15
@MaximEgorushkin Metade deles não entendeu este código ... funciona perfeitamente. No entanto, não gosto desta forma: é melhor, imo, incluir um cabeçalho e deixar que o padrão o defina.
Boiethios
9
Gente, pelo menos aprendam a maldita linguagem antes de ir contra as respostas perfeitamente corretas.
Frédéric Hamidi
11
Tom disse: "Existem 6 cabeçalhos de biblioteca padrão definindo a mesma coisa! Isso é insano! Precisamos de uma e apenas uma definição de size_t!" Um minuto depois, Mary disse: "Meu Deus! Existem 7 definições de size_tcabeçalhos de biblioteca padrão e um cabeçalho de projeto que Tom está editando! Provavelmente há mais nas bibliotecas de terceiros!" xkcd.com/927
6
Embora seja uma definição possível de size_t, isso não responde à verdadeira questão do OP: é como se eu perguntasse o cabeçalho onde FILEestá declarado e você sugerisse escrever o meu próprio.
edmz