2011-01-21 9 views
11

Mi piace la creazione di vettori con una data dimensione e valore, per esempio come questo:Come posso inizializzare un vettore std :: con un parametro di dimensione e avere ogni oggetto costruito in modo indipendente?

std::vector<std::string> names(10); 

Tuttavia, un paio di volte questo ha portato a risultati imprevisti. Ad esempio, nel seguente codice ogni UniqueNumber risulta avere lo stesso valore:

#include <iostream> 
#include <string> 
#include <vector> 


struct UniqueNumber 
{ 
    UniqueNumber() : mValue(sInstanceCount++) 
    { 
    } 

    inline unsigned int value() const 
    { 
     return mValue; 
    } 

private: 
    static unsigned int sInstanceCount; 
    unsigned int mValue; 
}; 


int UniqueNumber::sInstanceCount(0); 


int main() 
{ 
    std::vector<UniqueNumber> numbers(10); 
    for (size_t i = 0; i < numbers.size(); ++i) 
    { 
     std::cout << numbers[i].value() << " "; 
    } 
} 

Console uscita:

0 0 0 0 0 0 0 0 0 0 

ha senso quando si guarda il costruttore di std :: vector:

explicit vector(size_type __n, 
       const value_type& __value = value_type(), 
       const allocator_type& __a = allocator_type()); 

Apparentemente il vettore è stato inizializzato con copie dello stesso oggetto.

C'è anche un modo per costruire ogni oggetto predefinito?

risposta

4

generate o generate_n sono progettati appositamente per ciò che si vuole fare. Ecco un esempio completo:

#include <algorithm> 
#include <vector> 
#include <iterator> 
#include <iostream> 
using namespace std; 

class Foo 
{ 
public: 
    Foo() : n_(++water_) {}; 
    operator unsigned() const { return n_; } 
private: 
    static unsigned water_; 
    unsigned n_; 
}; 

Foo make_foo() 
{ 
    return Foo(); 
} 

unsigned Foo::water_ = 0; 

int main() 
{ 
    vector<Foo> v_foo; 
    generate_n(back_inserter(v_foo), 10, &make_foo); 
    copy(v_foo.begin(), v_foo.end(), ostream_iterator<unsigned>(cout, " ")); 
} 

uscita: 1 2 3 4 5 6 7 8 9 10

+0

Grazie, per offrire una soluzione non intrusiva. – StackedCrooked

+0

@StackedCrooked, in realtà è invadente, preferirei usare un oggetto per 'make_foo', sarebbe molto meglio. –

3

è necessario aggiungere un costruttore di copia:

UniqueNumber(const UniqueNumber& un) : mValue(sInstanceCount++) 
{ } 

Il costruttore di riempimento di std::vector non è chiamare il tuo costruttore di default. Piuttosto, chiama il costruttore predefinito che esiste implicitamente. Ovviamente il costruttore non incrementerà la variabile del contatore statico interno.

È inoltre necessario definire un operatore di assegnazione.

Tuttavia, l'utilizzo di un oggetto con un contatore statico interno con un std::vector produrrà risultati imprevisti, poiché il vettore può internamente copiare, costruire/assegnare l'oggetto per quanto lo ritiene opportuno. Quindi questo potrebbe interferire con la semantica della copia richiesta qui.

+0

uno che mi ha battuto ad esso :) – Zevan

+0

Beh, chiama il costruttore di default una volta, e quindi chiamando il costruttore di copia di default per ogni elemento aggiunge in. I risultati potrebbero essere ancora un po 'inaspettati in quanto i valori inizieranno da 1 e non da 0 se non lo notate. –

+0

Ciò risolverà il mio esempio, ma ora una copia non è più identica all'originale. Penso che sia un comportamento piuttosto non intuitivo. – StackedCrooked

4

Il vettore è la copia-costruzione degli elementi per l'inizializzazione.

Prova che:

#include <iostream> 
#include <string> 
#include <vector> 


struct UniqueNumber 
{ 
    UniqueNumber(bool automatic = true) 
     : mValue(automatic?sInstanceCount++:Special) 
    { } 

    UniqueNumber(UniqueNumber const& other) 
     : mValue(other.mValue==Special?sInstanceCount++:other.mValue) 
    { } 

    unsigned int value() const 
    { 
     return mValue; 
    } 

private: 
    static int sInstanceCount; 
    unsigned int mValue; 
    static unsigned int const Special = ~0U; 
}; 


int UniqueNumber::sInstanceCount(0); 


int main() 
{ 
    std::vector<UniqueNumber> numbers(10,UniqueNumber(false)); 
    for (size_t i = 0; i < numbers.size(); ++i) 
    { 
     std::cout << numbers[i].value() << " "; 
    } 
} 
+0

Non sono sicuro del motivo per cui hai aggiunto l'ulteriore argomento booleano al costruttore di UniqueNumber, ma chiaramente il tuo metodo è il più sicuro (rispetto ad altre proposizioni). –

+0

Il valore booleano è tale che l'utente può costruirlo in modo univoco o speciale (uno che è senza valore). –

+0

+1 per la creatività – StackedCrooked

4

Ci sono due modi per farlo.

Il bello, e C++ 0x modo, è quello di approfittare della lista inizializzatori:

std::vector<UniqueNumber> vec = { UniqueNumber(0), UniqueNumber(1) }; 

Ovviamente il problema qui è che è necessario specificare fuori in pieno.

In C++ 03, vorrei semplicemente utilizzare un ciclo:

std::vector<UniqueNumber> vec; 
vec.reserve(10); 
for (size_t i = 0; i != 10; ++i) { vec.push_back(UniqueNumber(i)); } 

Naturalmente questo potrebbe perfettamente ottenere incorporato in una funzione costruttore:

template <typename ValueType, typename Generator> 
std::vector<ValueType> generateVector(size_t size, Generator generator) 
{ 
    std::vector<ValueType> vec; 
    vec.reserve(size); 
    for (size_t i = 0; i != size; ++i) { vec.push_back(generator()); } 
    return vec; 
} 

Con NRVO calci in, si tratta di lo stesso, e ti permette di specificare i valori creati liberamente.

Lo stesso può essere ottenuto con l'STL generate_n in <algorithm> e includerò una sensazione di sintassi lambda.

std::vector<ValueType> vec; 
size_t count = 0; 
std::generate_n(std::back_inserter(vec), 10, [&]() { return Foo(++count); }); 

proposta da @Eugen nei commenti :)


Nota: WRT a "sorpresa", se si desidera avere elementi unici, forse, che un vector non è la struttura dei dati più adatto. E se effettivamente hai bisogno di un vector, considererei il wrapping in una classe per garantire, come invariato, che gli elementi siano unici.

+0

Mi ha colpito, stavo per proporre un uso di 'std :: generate_n (std :: back_inserter (numeri), 10, ...);' con '...' being è un "normale" 'CreateUniqueNumber' funzione o un lambda ... –

+0

@Eugen: Ah, ho pensato brevemente di aver già visto un simile costrutto, ma non riuscivo a ricordare che era una funzione STL! Ovviamente la domanda è sempre come passare da un semplice 'for' a un'espressione prolissa ... –

+0

Trovo generico più espressivo di un generico per e non più prolisso soprattutto con lambda in gioco

Problemi correlati