Pergunta

Eu mesmo estou convencido de que, em um projeto que estou trabalhando em inteiros assinados são a melhor escolha na maioria dos casos, mesmo que o valor contido dentro nunca pode ser negativo. (Simplificação da inversa para loops, menos chance de erros, etc., em particular para inteiros que só podem conter valores entre 0 e, digamos, 20 anos, de qualquer maneira.)

A maioria dos lugares onde isso vai mal é uma simples iteração de um std :: vector, muitas vezes isso costumava ser uma matriz no passado e foi alterado para um std :: vector mais tarde. Então, esses loops geralmente parecido com isto:

for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }

Uma vez que este padrão é usado tantas vezes, a quantidade de spam aviso do compilador sobre esta comparação entre o tipo assinados e não assinados tende a esconder advertências mais úteis. Note que nós definitivamente não têm vetores com mais elementos INT_MAX, e nota que até agora usamos duas maneiras de aviso correção do compilador:

for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }

Isso geralmente funciona, mas pode silenciosamente quebrar se o loop contém qualquer código como 'se (i-1> = 0) ...', etc.

for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }

Esta alteração não tem quaisquer efeitos secundários, mas faz o loop muito menos legível. (E é mais digitação.)

Então, eu vim com a seguinte idéia:

template <typename T> struct vector : public std::vector<T>
{
    typedef std::vector<T> base;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }
    int capacity() const { return base::capacity(); }

    vector()                  : base() {}
    vector(int n)             : base(n) {}
    vector(int n, const T& t) : base(n, t) {}
    vector(const base& other) : base(other) {}
};

template <typename Key, typename Data> struct map : public std::map<Key, Data>
{
    typedef std::map<Key, Data> base;
    typedef typename base::key_compare key_compare;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }

    int erase(const Key& k) { return base::erase(k); }
    int count(const Key& k) { return base::count(k); }

    map() : base() {}
    map(const key_compare& comp) : base(comp) {}
    template <class InputIterator> map(InputIterator f, InputIterator l) : base(f, l) {}
    template <class InputIterator> map(InputIterator f, InputIterator l, const key_compare& comp) : base(f, l, comp) {}
    map(const base& other) : base(other) {}
};

// TODO: similar code for other container types

O que você vê é basicamente as classes STL com os métodos que size_type retorno substituídas para retornar apenas 'int'. Os construtores são necessários porque estes não são herdadas.

O que você acha deste como um desenvolvedor, se você ver uma solução como esta em uma base de código existente?

Você acha que 'whaa, eles estão redefinindo o STL, o que é um enorme WTF!', Ou você acha que isso é uma boa solução simples para evitar erros e aumentar a legibilidade. Ou talvez você preferiria ver que tínhamos passado (meia) um ou dois dias em mudar todos esses loops para usar std :: vector <> :: iterator?

(em particular se esta solução foi combinada com a proibição da utilização de tipos sem sinal para nada, mas dados em bruto (por exemplo, não assinado carvão animal) e máscaras de bits.)

Foi útil?

Solução

Eu fiz este wiki comunidade ... Por favor, editá-lo. Não estou de acordo com o conselho contra "int" anymore. Agora eu vejo isso como não é mau.

Sim, eu concordo com Richard. Você nunca deve usar 'int' como a variável de contagem em um loop como aqueles. O seguinte é como você pode querer fazer várias voltas usando índices (althought há pouca razão para, ocasionalmente, isso pode ser útil).

Encaminhar

for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
    /* ... */
}

Backward

Você pode fazer isso, o que está perfeitamente definido behaivor:

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* ... */
}

Em breve, com c ++ 1x (próxima C ++ versão) saindo bem, você pode fazê-lo como este:

for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}

Decrementando abaixo de 0 fará com i para embrulhar ao redor, porque não está assinado.

Mas não assinado vai fazer erros gole no

Isso nunca deve ser um argumento para torná-lo o caminho errado (usando 'int').

Por que não usar std :: size_t acima?

O padrão C ++ define em 23.1 p5 Container Requirements, que T::size_type, por T sendo alguns Container, que este tipo é alguns implementação definida tipo integral sem sinal. Agora, usando std::size_t para i acima permitirá que erros sorver em silêncio. Se T::size_type é menor ou maior do que std::size_t, em seguida, ele irá transbordar i, ou nem mesmo chegar até a (std::size_t)-1 se someVector.size() == 0. Do mesmo modo, a condição do loop teria sido quebrado completamente.

Outras dicas

não derivam publicamente de contêineres STL. Eles têm destruidores não virtuais que invoca um comportamento indefinido se alguém exclui um de seus objetos através de um ponteiro-para a base. Se você deve derivar por exemplo a partir de um vetor, fazê-lo em privado e expor as peças que você precisa para expor com declarações using.

Aqui, eu tinha acabado de usar um size_t como a variável loop. É simples e legível. O cartaz que comentou que o uso de um índice int expõe como um n00b está correto. No entanto, usando um iterador para loop sobre um vetor expõe como um n00b um pouco mais experiente - alguém que não percebe que o operador subscrito para vetor é de tempo constante. (vector<T>::size_type é preciso, mas desnecessariamente verbose IMO).

Enquanto eu não acho "uso iterators, caso contrário, você olha n00b" é uma boa solução para o problema, decorrente de std :: vector parece muito pior do que isso.

Em primeiro lugar, os desenvolvedores espere vector ser std: .vector, e mapear a ser std :: map. Em segundo lugar, a sua solução não escala para outros recipientes, ou para outras classes / bibliotecas que interagem com os recipientes.

Sim, iteradores são feios, iteradoras laços não são muito bem legível, e typedefs cobrem apenas a bagunça. Mas, pelo menos, eles fazem escala, e eles são a solução canônica.

A minha solução? um STL-para-cada macro. Isso não é sem problemas (principalmente, é uma macro, eca), mas fica do outro lado da significado. Não é tão avançado como por exemplo esta , mas faz o trabalho.

Definitivamente usar um iterador. Em breve você será capaz de usar o tipo de 'auto', para melhor legibilidade (uma das suas preocupações) como este:

for (auto i = someVector.begin();
     i != someVector.end();
     ++i)

Skip the index

A abordagem mais fácil é para contornar o problema usando iteradores, gama baseado em loops, ou algoritmos:

for (auto it = begin(v); it != end(v); ++it) { ... }
for (const auto &x : v) { ... }
std::for_each(v.begin(), v.end(), ...);

Esta é uma boa solução se você realmente não precisa o valor do índice. Ele também lida com laços reversa facilmente.

Use um tipo não assinado apropriado

Outra abordagem é usar o tipo de tamanho do recipiente.

for (std::vector<T>::size_type i = 0; i < v.size(); ++i) { ... }

Você também pode usar std::size_t (de ). Há aqueles que (corretamente) apontam que std::size_t podem não ser do mesmo tipo que std::vector<T>::size_type (embora geralmente é). Você pode, no entanto, ter certeza de que size_type do contêiner vai caber em um std::size_t. Então está tudo bem, a menos que você usar determinados estilos de laços reversa. Meu estilo preferido para um ciclo reverso é esta:

for (std::size_t i = v.size(); i-- > 0; ) { ... }

Com este estilo, você pode usar com segurança std::size_t, mesmo que seja um tipo maior do que std::vector<T>::size_type. O estilo de loops reversos mostradas em algumas das outras respostas exigem lançando uma -1 a exatamente o tipo certo e, portanto, não pode usar o std::size_t mais fácil de tipo.

Use um tipo assinado (com cuidado!)

Se você realmente quiser usar um tipo assinado (ou se o seu guia de estilo praticamente exige um ), como int, então você pode usar este modelo função pequena que verifica o pressuposto subjacente em compilações de depuração e faz a tão explícita de conversão que você não receber a mensagem de aviso do compilador:

#include <cassert>
#include <cstddef>
#include <limits>

template <typename ContainerType>
constexpr int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

Agora você pode escrever:

for (int i = 0; i < size_as_int(v); ++i) { ... }

ou loops inversa da maneira tradicional:

for (int i = size_as_int(v) - 1; i >= 0; --i) { ... }

O truque size_as_int é apenas um pouco mais de digitação do que as malhas com as conversões implícitas, você começa o pressuposto subjacente verificado em tempo de execução, você silenciar o aviso do compilador com o elenco explícito, você recebe a mesma velocidade que não compilações de depuração porque quase certamente ser embutido, eo código objeto otimizado não deve ser maior porque o modelo não faz nada o compilador não foi já fazendo implicitamente.

Você está cismar o problema.

Usando uma variável size_t é preferível, mas se você não confiar em seus programadores a usar unsigned corretamente, vá com o elenco e apenas lidar com a feiúra. Obter um estagiário para mudá-los todos e não se preocupe com ele depois disso. Ligue avisos como erros e há novos se infiltrará. Seus laços pode ser "feio" agora, mas você pode entender que como as consequências da sua postura religiosa em assinado contra sem assinatura.

vector.size() retorna um size_t var, por isso a mudança int para size_t e ele deve estar bem.

A resposta de Richard é mais correto, exceto que é um monte de trabalho para um laço simples.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top