2010-02-18 13 views
12

Sto costruendo un'app che deve supportare supporti di array bidimensionali per contenere una griglia di dati. Ho una classe Map che contiene una griglia di dati 2d. Voglio usare i vettori piuttosto che gli array e mi chiedevo quali fossero le migliori pratiche per l'utilizzo dei vettori 2d. Dovrei avere un vettore di vettori di MapCells? o dovrebbe essere un vettore di vettori di indicatori di MapCells? o riferimenti a MapCells?C++ Std :: vector best practice a due dimensioni

class Map { 
private: 
    vector<vector<MapCell>> cells; 

public: 
    void loadMap() { 
     cells.clear(); 
     for (int i = 0; i < WIDTH; i++) { 
      //How should I be allocating these? 
      vector<MapCell> row(HEIGHT); 
      for (int j = 0; j < HEIGHT; j++) { 
       //Should I be dynamically allocating these? 
       MapCell cell; 
       row.push_back(cell); 
      } 
      cells.push_back(row); 
     } 
    } 
} 

Fondamentalmente ciò modo di fare questo sta per arrivare me nel minor numero di problemi (per quanto riguarda la gestione della memoria o qualsiasi altra cosa)?

+1

Non utilizzare 'vector >' a meno che non si desiderino array con bordi sfalsati – cobbal

+0

Desidero array sfilacciati, ma potrei utilizzare il padding alle estremità per ottenere questo risultato. – goatlinks

+1

Come hai preso in considerazione Boost.MultiArray? Guarda cosa dicono i documenti: http://www.boost.org/doc/libs/1_42_0/libs/multi_array/doc/user.html#sec_introduction – Manuel

risposta

8

Quando si desidera una griglia quadrata o 2d, fare qualcosa di simile a ciò che il compilatore fa per gli array multidimensionali (quelli reali, non una matrice di puntatori agli array) e memorizzare un singolo array grande che si indicizza correttamente.

Esempio utilizzando la classe Matrix di seguito:

struct Map { 
private: 
    Matrix<MapCell> cells; 

public: 
    void loadMap() { 
    Matrix<MapCell> cells (WIDTH, HEIGHT); 

    for (int i = 0; i < WIDTH; i++) { 
     for (int j = 0; j < HEIGHT; j++) { 
     // modify cells[i][j] 
     } 
    } 

    swap(this->cells, cells); 
    // if there's any problem loading, don't modify this->cells 
    // Matrix swap guarantees no-throw (because vector swap does) 
    // which is a common and important aspect of swaps 
    } 
}; 

Varianti di classi matrice abbondano, e ci sono molti modi per adeguare per uso specifico. Ecco un esempio in meno di 100 linee che si ottiene l'80% o più di quello che ti serve:

#include <algorithm> 
#include <memory> 
#include <vector> 

template<class T, class A=std::allocator<T> > 
struct Matrix { 
    typedef T value_type; 
    typedef std::vector<value_type, A> Container; 

    Matrix() : _b(0) {} 
    Matrix(int a, int b, value_type const& initial=value_type()) 
    : _b(0) 
    { 
    resize(a, b, initial); 
    } 
    Matrix(Matrix const& other) 
    : _data(other._data), _b(other._b) 
    {} 

    Matrix& operator=(Matrix copy) { 
    swap(*this, copy); 
    return *this; 
    } 

    bool empty() const { return _data.empty(); } 
    void clear() { _data.clear(); _b = 0; } 

    int dim_a() const { return _b ? _data.size()/_b : 0; } 
    int dim_b() const { return _b; } 

    value_type* operator[](int a) { 
    return &_data[a * _b]; 
    } 
    value_type const* operator[](int a) const { 
    return &_data[a * _b]; 
    } 

    void resize(int a, int b, value_type const& initial=value_type()) { 
    if (a == 0) { 
     b = 0; 
    } 
    _data.resize(a * b, initial); 
    _b = b; 
    } 

    friend void swap(Matrix& a, Matrix& b) { 
    using std::swap; 
    swap(a._data, b._data); 
    swap(a._b, b._b); 
    } 

    template<class Stream> 
    friend Stream& operator<<(Stream& s, Matrix const& value) { 
    s << "<Matrix at " << &value << " dimensions " 
     << value.dim_a() << 'x' << value.dim_b(); 
    if (!value.empty()) { 
     bool first = true; 
     typedef typename Container::const_iterator Iter; 
     Iter i = value._data.begin(), end = value._data.end(); 
     while (i != end) { 
     s << (first ? " [[" : "], ["); 
     first = false; 
     s << *i; 
     ++i; 
     for (int b = value._b - 1; b; --b) { 
      s << ", " << *i; 
      ++i; 
     } 
     } 
     s << "]]"; 
    } 
    s << '>'; 
    return s; 
    } 

private: 
    Container _data; 
    int _b; 
}; 
+0

+1, ma alcune cose farei in modo diverso: fornirei un costruttore senza argomenti che costruisce una matrice vuota e un costruttore di tre argomenti con la seconda dimensione che si imposta automaticamente su 1 e il valore iniziale predefinito per consentire al codice utente di eseguire 'Matrix a (5) 'e ottiene una matrice monodimensionale di dimensione 5 (invece di una matrice vuota come il precedente codice). Penso che 'clear()' dovrebbe applicare la tecnica di swap vettoriale per rilasciare la memoria acquisita: 'swap (_data, Container())'. Entrambi 'operator []' dovrebbe restituire riferimenti invece di puntatori. –

+1

Qual è il vantaggio di avere typedef T value_type? – fabrizioM

+0

@David: op [] restituisce i puntatori in modo da poter usare m [a] [b]. Il suo scopo è illustrativo e per prime approssimazioni, quindi anche se non sono necessariamente in disaccordo con i tuoi suggerimenti di ctor e swap, esito ad aggiungere troppe complicazioni. (L'inseritore repr è borderline, ma meno comune, quindi la complicazione ha un valore più elevato IMHO.) @fabrizioM: stesso vantaggio del vettore :: value_type --- Sto usando un sottoinsieme dell'interfaccia di sequenza STL e potrebbe essere facilmente esteso per includerne di più. –

3

Se l'intera matrice ha larghezza e altezza per lo più costanti, è possibile utilizzare anche un singolo vector e le celle di indirizzo con (row * columnCount) + column. In questo modo l'intera operazione verrà archiviata in un singolo blocco di memoria anziché in diversi blocchi frammentati per ogni riga. (Anche se stai facendo la cosa giusta per concludere questo concetto in una nuova classe - Sto solo parlando dell'implementazione dietro le quinte.)

Un vettore di vettori ha la sfortunata proprietà che se tu inserire una riga nella parte superiore, std::vector eseguirà una costruzione di copia (o assegnazione, possibilmente) per tutte le altre righe mentre le sposta di una posizione. Ciò a sua volta comporta la riallocazione dello spazio di archiviazione per ogni riga e la copia individuale degli elementi nelle celle di ogni riga. (C++ 0x sarà probabilmente meglio in questo.)

Se sai che farai spesso questo tipo di cose, il vantaggio di un unico grande blocco di memoria è che puoi inserire una nuova riga in cima e std::vector dovrà solo spostare tutte le celle in avanti di columnCount posizioni, quindi ridurrà seriamente il numero di operazioni heap (liberando/riallocando i singoli blocchi).

Sebbene, come suggerito, un vettore di puntatori ai vettori avrebbe l'ulteriore vantaggio che sarebbe necessario solo spostare in avanti molti valori di puntatore e la dimensione del blocco contenente tutti i puntatori di riga sarà molto più piccola, riducendo ulteriormente l'impatto delle operazioni heap.

Ovviamente, l'unico modo per essere sicuri dell'impatto effettivo di queste cose sulle prestazioni di un'applicazione è crearlo con varie implementazioni e confrontarle. Questo è il motivo per cui stai facendo esattamente la cosa giusta nascondendo questi dettagli all'interno di una nuova classe.

0

Usare un vettore e tradurre le 2 dimensioni a una dimensione. E.g. se la tua matrice ha una larghezza di W e un'altezza di H, allora la mappatura di x, y all'indice nel vettore è semplicemente x * W + y.

Se la matrice è scarsa, si consiglia di utilizzare una mappa std :: dove la chiave è una coppia (xey).

Problemi correlati