size_t ou int para dimensões, índice, etc.

15

Em C ++, size_t (ou, mais corretamente T::size_type que é "geralmente" size_t ; isto é, um unsigned type) é usado como o valor de retorno para size() , o argumento para operator[] , etc. (consulte std::vector , et. al.)

Por outro lado, as linguagens .NET usam int (e, opcionalmente, long ) para o mesmo propósito; na verdade, os idiomas compatíveis com CLS são não obrigatórios para oferecer suporte a tipos não assinados .

Dado que o .NET é mais recente que o C ++, algo me diz que pode haver problemas usando unsigned int mesmo para coisas que "possivelmente não podem" ser negativas como um índice ou comprimento de matriz. A abordagem do C ++ é "artefato histórico" para compatibilidade com versões anteriores? Ou existem tradeoffs de projeto reais e significativos entre as duas abordagens?

Por que isso importa? Bem ... o que devo usar para uma nova classe multidimensional em C ++; size_t ou int ?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};
    
por Ðаn 13.12.2016 / 21:58
fonte

4 respostas

9

Given that .NET is newer than C++, something tells me that there may be problems using unsigned int even for things that "can't possibly" be negative like an array index or length.

Sim. Para determinados tipos de aplicativos, como processamento de imagens ou processamento de matrizes, geralmente é necessário acessar elementos relativos à posição atual:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

Nesses tipos de aplicativos, você não pode executar a verificação de intervalo com números inteiros sem sinal sem pensar cuidadosamente:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

Em vez disso, você precisa reorganizar sua expressão de verificação de intervalo. Essa é a principal diferença. Os programadores também devem lembrar as regras de conversão de números inteiros. Em caso de dúvida, leia novamente link

Muitos aplicativos não precisam usar índices de matriz muito grandes, mas precisam executar verificações de intervalo. Além disso, muitos programadores não são treinados para fazer essa ginástica de rearranjo de expressão. Uma única oportunidade perdida abre a porta para uma exploração.

O C # é realmente projetado para aqueles aplicativos que não precisarão de mais de 2 ^ 31 elementos por matriz. Por exemplo, um aplicativo de planilha não precisa lidar com muitas linhas, colunas ou células. O C # lida com o limite superior ao ter aritmética verificada opcional que pode ser ativada para um bloco de código com uma palavra-chave sem mexer nas opções do compilador. Por esse motivo, C # favorece o uso de inteiro assinado. Quando essas decisões são consideradas, faz sentido.

C ++ é simplesmente diferente e é mais difícil obter o código correto.

Em relação à importância prática de permitir a aritmética assinada para remover uma violação potencial do "princípio do menos espanto", um caso em questão é o OpenCV, que usa inteiro assinado de 32 bits para índice de elemento de matriz, tamanho de matriz, contagem de canal de pixel, O processamento de imagens é um exemplo de domínio de programação que utiliza strongmente o índice relativo de array. O underflow de inteiro não assinado (resultado negativo envolvido) complicará seriamente a implementação do algoritmo.

    
por 14.12.2016 / 05:57
fonte
14

Essa resposta realmente depende de quem vai usar seu código e de quais padrões eles querem ver.

size_t é um tamanho inteiro com um propósito:

The type size_t is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object. (C++11 specification 18.2.6)

Assim, sempre que você quiser trabalhar com o tamanho de objetos em bytes, você deve usar size_t . Agora, em muitos casos, você não está usando essas dimensões / índices para contar bytes, mas a maioria dos desenvolvedores optou por usar size_t lá para consistência.

Note que você deve sempre usar size_t se a sua aula tiver a aparência de uma classe STL. Todas as classes STL na especificação usam size_t . É válido para o compilador typedef size_t ser unsigned int e também é válido para que seja digitado como unsigned long . Se você usa int ou long diretamente, você acabará encontrando compiladores onde uma pessoa que acha que sua classe seguiu o estilo da STL fica presa porque você não seguiu o padrão.

Quanto ao uso de tipos assinados, há algumas vantagens:

  • Nomes mais curtos - é muito fácil para as pessoas digitar int , mas é muito mais difícil desordenar o código com unsigned int .
  • Um inteiro para cada tamanho - Há apenas um inteiro compatível com CLS de 32 bits, que é Int32. Em C ++, há dois ( int32_t e uint32_t ). Isso pode tornar a interoperabilidade da API mais simples

A grande desvantagem dos tipos assinados é óbvia: você perde metade do seu domínio. Um número assinado não pode contar tão alto quanto um número não assinado. Quando o C / C ++ apareceu, isso foi muito importante. Era necessário ser capaz de lidar com a capacidade total do processador e, para isso, era necessário usar números não assinados.

Para os tipos de aplicativos .NET segmentados, não havia uma necessidade tão grande de um índice sem sinal de domínio completo. Muitos dos propósitos para tais números são simplesmente inválidos em uma linguagem gerenciada (o pool de memória vem à mente). Além disso, quando o .NET surgiu, os computadores de 64 bits eram claramente o futuro. Estamos muito longe de precisar do alcance total de um inteiro de 64 bits, então sacrificar um bit não é tão doloroso quanto era antes. Se você realmente precisa de 4 bilhões de índices, simplesmente mude para usar inteiros de 64 bits. Na pior das hipóteses, você o executa em uma máquina de 32 bits e é um pouco lento.

Eu vejo o comércio como uma conveniência. Se você tiver poder de computação suficiente para não se importar em gastar um pouco do seu tipo de índice que nunca será usado, é conveniente digitar int ou long e sair dessa. Se você acha que realmente queria o último pedaço, então provavelmente deveria ter prestado atenção à assinatura de seus números.

    
por 13.12.2016 / 22:45
fonte
4

Acho que a resposta do rwong acima já destaca de maneira excelente os problemas.

Vou adicionar meu 002:

  • size_t , ou seja, um tamanho que ...

    can store the maximum size of a theoretically possible object of any type (including array).

    ... é necessário apenas para índices de intervalo quando sizeof(type)==1 , ou seja, se você estiver lidando com tipos de byte ( char ). (Mas, observamos, pode ser menor que um tipo de ptr :

  • Como tal, xxx::size_type poderia ser usado em 99,9% dos casos, mesmo se fosse um tipo de tamanho assinado. (compare ssize_t )
  • O fato de que std::vector e amigos escolheram size_t , um tipo não assinado , pois o tamanho e a indexação são considerado por alguns como uma falha de design. Eu concordo. (Sério, leve 5 minutos e assista a conversa relâmpago CppCon 2016: Jon Kalb “sem assinatura: Uma diretriz para um melhor código” .)
  • Quando você cria uma API C ++ hoje, você está em um local restrito: use size_t para ser consistente com a biblioteca padrão ou use ( assinado ) intptr_t ou ssize_t para cálculos de indexação fáceis e menos propensos a bugs.
  • Não use int32 ou int64 - use intptr_t se desejar assinar, e quiser o tamanho da palavra da máquina ou use ssize_t .

Para responder diretamente à pergunta, não é inteiramente um "artefato histórico", como a questão teórica de precisar abordar mais da metade do espaço de "indexação", ou endereço, deve ser, aehm, endereçado de alguma forma em uma linguagem de baixo nível como C ++.

Em retrospectiva, eu, pessoalmente , penso, é uma falha de design que a Biblioteca Padrão usa unsigned size_t em todo o lugar mesmo quando não representa um tamanho de memória bruta, mas uma capacidade de dados digitados, como para as coleções:

  • dadas regras de promoção de inteiro de C ++ s - >
  • tipos não assinados simplesmente não são bons candidatos para tipos "semânticos" para algo como um tamanho que é semanticamente não assinado.

Vou repetir o conselho de Jon aqui:

  • Selecione tipos para as operações que eles suportam (não o intervalo de valores). (* 1)
  • Não use tipos não assinados na sua API. Isso esconde bugs sem benefício de lucro.
  • Não use "sem sinal" para quantidades. (* 2)

(* 1), ou seja, unsigned == bitmask, nunca faça matemática (aqui chega a primeira exceção - você pode precisar de um contador que envolva - este deve ser um tipo não assinado).

(* 2) quantidades que significam algo que você conta e / ou faz matemática.

    
por 14.12.2016 / 22:44
fonte
0

Acrescento que, por motivos de desempenho, normalmente uso size_t para garantir que os erros de cálculo causam um underflow, o que significa que ambas as verificações de range (abaixo de zero e acima de size) podem ser reduzidas a um :

usando int assinado:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

usando int não assinado:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}
    
por 20.12.2016 / 13:40
fonte

Tags