Quando usar std :: size_t?

201

Eu só estou querendo saber se devo usar std::size_tpara loops e outras coisas em vez de int? Por exemplo:

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

Em geral, qual é a melhor prática em relação a quando usar std::size_t?

nhaa123
fonte

Respostas:

186

Uma boa regra geral é para qualquer coisa que você precise comparar na condição de loop com algo que é naturalmente um std::size_t.

std::size_té o tipo de qualquer sizeofexpressão e, como é garantido, é capaz de expressar o tamanho máximo de qualquer objeto (incluindo qualquer matriz) em C ++. Por extensão, também é garantido que seja grande o suficiente para qualquer índice de matriz, por isso é um tipo natural para um loop por índice em uma matriz.

Se você está apenas contando até um número, pode ser mais natural usar o tipo de variável que contém esse número ou um intou unsigned int(se for grande o suficiente), pois esses devem ter um tamanho natural para a máquina.

CB Bailey
fonte
41
Vale ressaltar que o não uso size_tquando necessário pode levar a erros de segurança .
BlueRaja - Danny Pflughoeft
5
O int não é apenas "natural", mas a mistura de tipos assinado e não assinado também pode levar a erros de segurança. Índices não assinados são difíceis de manipular e um bom motivo para usar uma classe vetorial personalizada.
Jo
2
@JoSo Também ssize_thá valores assinados.
EntangledLoops
70

size_té o tipo de resultado do sizeofoperador.

Use size_tpara variáveis ​​que modelam tamanho ou índice em uma matriz. size_ttransmite semântica: você imediatamente sabe que representa um tamanho em bytes ou um índice, em vez de apenas outro número inteiro.

Além disso, usar size_tpara representar um tamanho em bytes ajuda a tornar o código portátil.

Gregory Pakosz
fonte
32

O size_ttipo deve especificar o tamanho de algo, por isso é natural usá-lo, por exemplo, obtendo o comprimento de uma sequência e processando cada caractere:

for (size_t i = 0, max = strlen (str); i < max; i++)
    doSomethingWith (str[i]);

Você faz tem que tomar cuidado para condições de contorno, é claro, já que é um tipo não assinado. O limite na extremidade superior geralmente não é tão importante, pois o máximo geralmente é grande (embora seja possível chegar lá). A maioria das pessoas apenas usa um intpara esse tipo de coisa porque raramente possui estruturas ou matrizes que são grandes o suficiente para exceder a capacidade disso int.

Mas cuidado com coisas como:

for (size_t i = strlen (str) - 1; i >= 0; i--)

o que causará um loop infinito devido ao comportamento de quebra de valores não assinados (embora eu tenha visto compiladores alertarem contra isso). Isso também pode ser aliviado pelo (um pouco mais difícil de entender, mas pelo menos imune a problemas de empacotamento):

for (size_t i = strlen (str); i-- > 0; )

Ao mudar o decremento para um efeito colateral pós-verificação da condição de continuação, ele faz a verificação da continuação do valor antes do decremento, mas ainda usa o valor decrementado dentro do loop (e é por isso que o loop é executado em len .. 1vez de len-1 .. 0).

paxdiablo
fonte
14
A propósito, é uma prática ruim chamar strlencada iteração de um loop. :) Você pode fazer algo como isto:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil
1
Mesmo se fosse um tipo assinado, você deve observar as condições de contorno, talvez ainda mais porque o excesso de número inteiro assinado é um comportamento indefinido.
Adrian McCarthy
2
A contagem regressiva correta pode ser feita da seguinte maneira (infame):for (size_t i = strlen (str); i --> 0;)
Jo So
1
@ JoSo, isso é realmente um truque interessante, embora não tenha certeza de gostar da introdução do -->operador "vai para" (consulte stackoverflow.com/questions/1642028/… ). Incorporou sua sugestão na resposta.
21416
Você pode fazer um simples if (i == 0) break;, no final do loop for (por exemplo, for (size_t i = strlen(str) - 1; ; --i)(I como o seu melhor embora, mas apenas querendo saber se isso iria funcionar tão bem)..
RastaJedi
13

Por definição, size_té o resultado do sizeofoperador. size_tfoi criado para se referir a tamanhos.

O número de vezes que você faz alguma coisa (10, no seu exemplo) não é sobre tamanhos, então por que usar size_t? intou unsigned intdeve estar ok.

Claro que também é relevante o que você faz com o iinterior do loop. Se você o passar para uma função que faça um unsigned int, por exemplo, escolha unsigned int.

De qualquer forma, recomendo evitar conversões implícitas de tipo. Torne explícitas todas as conversões de tipo.

Daniel Daranas
fonte
10

size_té uma maneira muito legível de especificar a dimensão de tamanho de um item - comprimento de uma string, quantidade de bytes que um ponteiro leva etc. Também é portátil em várias plataformas - você verá que 64 bits e 32 bits se comportam bem com as funções do sistema e size_t- algo que unsigned intpode não funcionar (por exemplo, quando você deve usarunsigned long

Ofir
fonte
9

resposta curta:

quase nunca

resposta longa:

Sempre que você precisar de um vetor de char maior que 2gb em um sistema de 32 bits. Em todos os outros casos de uso, usar um tipo assinado é muito mais seguro do que usar um tipo não assinado.

exemplo:

std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous

// do some bounds checking
if( i - 1 < 0 ) {
    // always false, because 0-1 on unsigned creates an underflow
    return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
    // if i already had an underflow, this becomes true
    return RIGHT_BORDER;
}

// now you have a bug that is very hard to track, because you never 
// get an exception or anything anymore, to detect that you actually 
// return the false border case.

return calc_something(data[i-1], data[i], data[i+1]);

O equivalente assinado de size_té ptrdiff_t, não int. Mas o uso intainda é muito melhor na maioria dos casos do que size_t. ptrdiff_télong em sistemas de 32 e 64 bits.

Isso significa que você sempre precisa converter de e para size_t sempre que interagir com um std :: containers, o que não é muito bonito. Mas em uma conferência nativa em andamento, os autores do c ++ mencionaram que projetar std :: vector com um size_t não assinado foi um erro.

Se o seu compilador fornecer avisos sobre conversões implícitas de ptrdiff_t para size_t, você poderá explicitá-lo com a sintaxe do construtor:

calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);

se quiser apenas iterar uma coleção, sem limites, use o intervalo com base em:

for(const auto& d : data) {
    [...]
}

aqui algumas palavras de Bjarne Stroustrup (autor C ++) em nativo

Para algumas pessoas, esse erro de design assinado / não assinado no STL é motivo suficiente, para não usar o std :: vector, mas uma implementação própria.

Arne
fonte
1
Eu entendo de onde eles vêm, mas ainda acho estranho escrever for(int i = 0; i < get_size_of_stuff(); i++). Agora, claro, você pode não querer fazer muitos loops brutos, mas - vamos lá, você os usa também.
einpoklum
A única razão pela qual eu uso loops brutos é porque a biblioteca de algoritmos c ++ foi projetada muito mal. Existem idiomas, como o Scala, que têm uma biblioteca muito melhor e mais evoluída para operar em coleções. Em seguida, o caso de uso de loops brutos é praticamente eliminado. Também existem abordagens para melhorar o c ++ com um novo e melhor STL, mas duvido que isso aconteça na próxima década.
Arne16:
1
Eu recebo isso sem sinal i = 0; afirmar (i-1, MAX_INT); mas não entendo por que você diz "se eu já tinha um fluxo insuficiente, isso se torna verdadeiro" porque o comportamento da aritmética em entradas não assinadas é sempre definido, ou seja. o resultado é o módulo de resultado do tamanho do maior número inteiro representável. Portanto, se i == 0, então i-- se torna MAX_INT e então i ++ se torna 0 novamente.
mabraham
@mabraham Eu olhei atentamente, e você está certo, meu código não é o melhor para mostrar o problema. Normalmente isso é x + 1 < yequivalente a x < y - 1, mas eles não estão com números inteiros não enviados. Isso pode facilmente introduzir bugs quando coisas são transformadas e assumidas como equivalentes.
Arne
8

Use std :: size_t para indexar / contar matrizes no estilo C.

Para contêineres STL, você terá (por exemplo) vector<int>::size_type, que deve ser usado para indexar e contar elementos vetoriais.

Na prática, eles geralmente são ints não assinados, mas isso não é garantido, especialmente ao usar alocadores personalizados.

Peter Alexander
fonte
2
Com o gcc no linux, std::size_tnormalmente é unsigned long(8 bytes em sistemas de 64 bits) e não unisgned int(4 bytes).
Rafak
5
As matrizes no estilo C não são indexadas size_t, pois os índices podem ser negativos. Alguém poderia usar size_tpara sua própria instância de tal matriz, se não quiser ser negativo.
Johannes Schaub - litb 24/12/2009
As comparações nos u64s são tão rápidas quanto as comparações nos u32s? Eu cronometrei severas penalidades de desempenho por usar u8s e u16s como sentinelas de loop, mas não sei se a Intel conseguiu agir em conjunto nos 64s.
Crashworks
2
Como a indexação de array no estilo C é equivalente a usar o operador +em ponteiros, parece que esse ptrdiff_té o único a ser usado para índices.
Pavel Minaev 24/12/2009
8
Quanto a vector<T>::size_type(e idem para todos os outros contêineres), é realmente bastante inútil, porque é efetivamente garantido que size_tele é - foi digitado e Allocator::size_type, quanto a restrições em relação a contêineres, consulte 20.1.5 / 4 - em particular, size_typedeve ser size_te difference_typedeve ser ptrdiff_t. Obviamente, o padrão std::allocator<T>atende a esses requisitos. Então, basta usar o mais curto size_te não se preocupam com o resto do lote :)
Pavel Minaev
7

Em breve, a maioria dos computadores terá arquiteturas de 64 bits com SO de 64 bits executando programas operando em contêineres de bilhões de elementos. Em seguida, você deve usar em size_tvez de intcomo índice de loop, caso contrário, seu índice contornará no elemento 2 ^ 32: th, nos sistemas de 32 e 64 bits.

Prepare-se para o futuro!

Nordlöw
fonte
Seu argumento chega apenas ao significado de que é preciso um long inte não um int. Se size_té relevante em um sistema operacional de 64 bits, era igualmente relevante em um sistema operacional de 32 bits.
einpoklum
4

Ao usar size_t, tenha cuidado com a seguinte expressão

size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
    cout << containner[i-x] << " " << containner[i+x] << endl;
}

Você ficará falso na expressão if, independentemente do valor que você tem para x. Levei vários dias para perceber isso (o código é tão simples que não fiz teste de unidade), embora demore apenas alguns minutos para descobrir a origem do problema. Não tenho certeza se é melhor fazer um elenco ou usar zero.

if ((int)(i-x) > -1 or (i-x) >= 0)

Ambas as formas devem funcionar. Aqui está o meu teste

size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;

A saída: i-7 = 18446744073709551614 (int) (i-7) = - 2

Eu gostaria dos comentários de outros.

Kemin Zhou
fonte
2
observe que (int)(i - 7)é um underflow que é convertido intposteriormente, enquanto int(i) - 7não é um underflow desde que você primeiro converte ipara um inte depois subtrai 7. Além disso, achei seu exemplo confuso.
hochl
Meu argumento é que int geralmente é mais seguro quando você faz subtrações.
Kemin Zhou
4

size_t é retornado por várias bibliotecas para indicar que o tamanho desse contêiner é diferente de zero. Você o usa quando voltar: 0

No entanto, no seu exemplo acima, fazer um loop em size_t é um bug em potencial. Considere o seguinte:

for (size_t i = thing.size(); i >= 0; --i) {
  // this will never terminate because size_t is a typedef for
  // unsigned int which can not be negative by definition
  // therefore i will always be >= 0
  printf("the never ending story. la la la la");
}

o uso de números inteiros não assinados tem o potencial de criar esses tipos de problemas sutis. Portanto, imho prefiro usar size_t apenas quando interajo com contêineres / tipos que exigem isso.

ascotan
fonte
Everone parece usar size_t em loop sem se preocupar com esse bug, e eu aprendi isso da maneira mais difícil
Pranjal Gupta
-2

size_t é um tipo não assinado que pode conter o valor inteiro máximo para sua arquitetura, portanto, é protegido contra estouros de número inteiro devido a assinatura (int assinado 0x7FFFFFFF incremento de 1 fornecerá -1) ou tamanho curto (short sem sinal int 0xFFFF incrementado por 1 fornecerá a você 0)

É usado principalmente na aritmética de indexação / loops / endereço de array e assim por diante. Funções como memset()e similares aceitam size_tapenas, porque teoricamente você pode ter um bloco de memória de tamanho2^32-1 (na plataforma de 32 bits).

Para loops tão simples, não se preocupe e use apenas int.

Wizzard
fonte
-3

size_t é um tipo integral não assinado, que pode representar o maior número inteiro no sistema. Use-o somente se você precisar de matrizes, matrizes muito grandes etc.

Algumas funções retornam um size_t e seu compilador avisará se você tentar fazer comparações.

Evite isso usando um tipo de dados assinado / não assinado apropriado ou simplesmente tipecast para um hack rápido.

Rei Macaco
fonte
4
Use-o apenas se desejar evitar bugs e falhas de segurança.
Craig McQueen
2
Pode não ser realmente capaz de representar o maior número inteiro no seu sistema.
Adrian McCarthy
-4

size_t é sem sinal int. portanto, sempre que você quiser não assinado int, poderá usá-lo.

Eu uso quando eu quero especificar o tamanho da matriz, contador ect ...

void * operator new (size_t size); is a good use of it.
Ashish
fonte
10
Na verdade, não é necessariamente o mesmo que int não assinado. Não está assinado, mas pode ser maior (ou acho que menor), embora eu não conheça nenhuma plataforma onde isso seja verdade) do que um int.
Todd Gamblin
Por exemplo, em uma máquina de 64 bits size_tpode ser um número inteiro de 64 bits sem sinal, enquanto em uma máquina de 32 bits é apenas um número inteiro sem sinal de 32 bits.
HerpDerpington