Domanda

Io stesso sono convinto che in un progetto sto lavorando su numeri interi firmati sono la scelta migliore nella maggior parte dei casi, anche se il valore contenuto all'interno non può mai essere negativo. (Inversione più semplice per i loop, meno possibilità di bug, ecc., In particolare per numeri interi che possono contenere solo valori compresi tra 0 e, diciamo, 20, comunque.)

La maggior parte dei luoghi in cui ciò non funziona è una semplice iterazione di uno std :: vector, spesso in passato era un array ed è stato cambiato in uno std :: vector in seguito. Quindi questi loop generalmente sembrano così:

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

Poiché questo modello viene utilizzato così spesso, la quantità di spam di avviso del compilatore su questo confronto tra tipo con segno e senza segno tende a nascondere avvisi più utili. Nota che sicuramente non abbiamo vettori con più di INT_MAX elementi e nota che fino ad ora abbiamo usato due modi per correggere l'avviso del compilatore:

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

Questo di solito funziona, ma potrebbe interrompersi silenziosamente se il ciclo contiene un codice come 'if (i-1 > = 0) ...', ecc.

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

Questa modifica non ha effetti collaterali, ma rende il loop molto meno leggibile. (Ed è più digitando.)

Quindi mi è venuta in mente la seguente idea:

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

Quello che vedi sono fondamentalmente le classi STL con i metodi che restituiscono size_type sovrascritti per restituire solo 'int'. I costruttori sono necessari perché non sono ereditati.

Cosa pensi di questo come sviluppatore, se vedessi una soluzione come questa in una base di codice esistente?

Penseresti 'whaa, stanno ridefinendo la STL, che enorme WTF!', o pensi che questa sia una bella soluzione semplice per prevenire bug e aumentare la leggibilità. O forse preferiresti vedere che avevamo trascorso (mezza) circa una giornata per cambiare tutti questi loop per usare std :: vector & Lt; & Gt; :: iterator?

(In particolare se questa soluzione fosse combinata con il divieto dell'uso di tipi non firmati per qualsiasi cosa tranne i dati non elaborati (ad esempio caratteri non firmati) e maschere di bit.)

È stato utile?

Soluzione

Ho creato questo wiki della community ... Per favore, modificalo. Non sono d'accordo con il consiglio contro & Quot; int & Quot; più. Ora lo vedo come non male.

Sì, sono d'accordo con Richard. Non dovresti mai usare 'int' come variabile di conteggio in un ciclo come quelli. Quello che segue è come potresti voler fare vari loop usando gli indici (anche se ci sono poche ragioni per farlo, a volte questo può essere utile).

Avanti

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

Indietro

Puoi farlo, che è un comportamento perfettamente definito:

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

Presto, con c ++ 1x (prossima versione C ++) in arrivo, puoi farlo in questo modo:

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

La riduzione al di sotto di 0 comporterà il riavvolgimento di i, poiché non è firmato.

Ma unsigned farà insinuare i bug

Questo non dovrebbe mai essere un argomento per renderlo nel modo sbagliato (usando 23.1 p5 Container Requirements).

Perché non usare std :: size_t sopra?

Lo standard C ++ definisce in T::size_type, che T, per Container essendo un po 'std::size_t, che questo tipo è un tipo integrale senza segno definito dall'implementazione. Ora, usando i per (std::size_t)-1 sopra, i bug si insinueranno silenziosamente. Se someVector.size() == 0 è minore o maggiore di <=>, allora trabocca <=> o non si alza nemmeno fino a <=> se <=>. Allo stesso modo, le condizioni del loop sarebbero state completamente interrotte.

Altri suggerimenti

Non derivare pubblicamente dai contenitori STL. Hanno distruttori non virtuali che invocano comportamenti indefiniti se qualcuno elimina uno dei tuoi oggetti attraverso una base puntatore a. Se devi derivare ad es. da un vettore, fallo privatamente ed esponi le parti che devi esporre con using dichiarazioni.

Qui, userei solo size_t come variabile del ciclo. È semplice e leggibile. Il poster che ha commentato che l'utilizzo di un indice int ti espone come un n00b è corretto. Tuttavia, l'uso di un iteratore per scorrere su un vettore ti espone come un n00b leggermente più esperto, uno che non si rende conto che l'operatore di sottoscrizione per il vettore è un tempo costante. (vector<T>::size_type è preciso, ma inutilmente prolisso IMO).

Anche se non credo " usa iteratori, altrimenti sembri n00b " è una buona soluzione al problema, derivante da std :: vector appare molto peggio di così.

Innanzitutto, gli sviluppatori prevedono che il vettore sia std: .vector e la mappa sia std :: map. In secondo luogo, la soluzione non viene ridimensionata per altri contenitori o per altre classi / librerie che interagiscono con i contenitori.

Sì, gli iteratori sono brutti, i loop di iteratori non sono molto ben leggibili e i typedef nascondono solo il disordine. Ma almeno, scalano e sono la soluzione canonica.

La mia soluzione? una macro stl per ogni. Questo non è senza problemi (principalmente, è una macro, schifo), ma riesce a capire il significato. Non è avanzato come ad es. questo , ma fa il lavoro.

Sicuramente usa un iteratore. Presto sarai in grado di utilizzare il tipo "auto", per una migliore leggibilità (una delle tue preoccupazioni) in questo modo:

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

Salta l'indice

L'approccio più semplice è quello di eludere il problema usando iteratori, range-based per loop o algoritmi:

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

Questa è una buona soluzione se in realtà non hai bisogno del valore dell'indice. Gestisce facilmente anche i cicli inversi.

Utilizza un tipo non firmato appropriato

Un altro approccio consiste nell'utilizzare il tipo di dimensione del contenitore.

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

Puoi anche usare std::size_t (da < cstddef >). C'è chi afferma (correttamente) che std::vector<T>::size_type potrebbe non essere dello stesso tipo di size_type (sebbene di solito lo sia). Tuttavia, puoi essere certo che il contenitore int si inserirà in un size_as_int. Quindi tutto va bene, a meno che non si utilizzino determinati stili per i cicli inversi. Il mio stile preferito per un ciclo inverso è questo:

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

Con questo stile, puoi tranquillamente utilizzare <=>, anche se è un tipo più grande di <=>. Lo stile dei cicli inversi mostrato in alcune delle altre risposte richiede il lancio di un -1 esattamente nel tipo giusto e quindi non è possibile utilizzare il <=>.

più facile da digitare.

Usa un tipo firmato (con attenzione!)

Se vuoi davvero usare un tipo firmato (o se la guida di stile praticamente ne richiede una ), come <=>, quindi puoi usare questo piccolo modello di funzione che controlla l'assunto sottostante nelle build di debug e rende esplicita la conversione in modo da non ricevere l'avviso del compilatore messaggio:

#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);
}

Ora puoi scrivere:

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

O invertire i cicli nel modo tradizionale:

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

Il trucco <=> sta scrivendo solo leggermente più dei loop con le conversioni implicite, ottieni l'assunto sottostante verificato in fase di esecuzione, metti in silenzio l'avviso del compilatore con il cast esplicito, ottieni la stessa velocità delle build non di debug perché sarà quasi sicuramente in linea e il codice oggetto ottimizzato non dovrebbe essere più grande perché il modello non fa nulla che il compilatore non stesse già facendo implicitamente.

Stai pensando troppo al problema.

È preferibile usare una variabile size_t, ma se non ti fidi che i tuoi programmatori utilizzino correttamente unsigned, vai con il cast e gestisci semplicemente la bruttezza. Chiedi a un tirocinante di cambiarli tutti e dopo non preoccuparti. Attiva gli avvisi come errori e non si insinueranno quelli nuovi. I tuoi loop potrebbero essere & Quot; brutto & Quot; ora, ma puoi capirlo come le conseguenze della tua posizione religiosa su firmato contro non firmato.

vector.size() restituisce un size_t var, quindi basta cambiare int in <=> e dovrebbe andare bene.

La risposta di Richard è più corretta, tranne per il fatto che è un sacco di lavoro per un semplice ciclo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top