2010-09-20 18 views
11

Sto eseguendo il porting del codice che utilizza una matrice molto ampia di float, che può causare errori di malloc da c a C++. Ho fatto una domanda sul fatto che dovrei usare vettori o deque e Niki Yoshiuchi generosamente mi ha offerto questo esempio di un tipo in modo sicuro avvolto:Come definire un doppio bracket/doppio operatore Iterator, simile al vettore di vettori?

template<typename T> 
class VectorDeque 
{ 
private: 
    enum TYPE { NONE, DEQUE, VECTOR }; 
    std::deque<T> m_d; 
    std::vector<T> m_v; 
    TYPE m_type; 
    ... 
public: 
    void resize(size_t n) 
    { 
    switch(m_type) 
    { 
     case NONE: 
     try 
     { 
     m_v.resize(n); 
     m_type = VECTOR; 
     } 
     catch(std::bad_alloc &ba) 
     { 
     m_d.resize(n); 
     m_type = DEQUE; 
     } 
     break; 
    } 
    } 
}; 

avevo bisogno di un vettore 2D di vettori/deque di deque, quindi ho modificato al codice seguente:

template<typename T> 
class VectorDeque 
{ 
private: 
    enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR }; 
    std::deque<std::deque<T> > x_d,y_d,z_d; 
    std::vector<std::vector<T> > x_v,y_v,z_v; 
    TYPE my_container; 
public: 
    void resize(size_t num_atoms, size_t num_frames) 
    { 
    switch(m_type) 
    { 
     case NONE: 
     try 
     { 
     x_v.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    x_v[counter].resize(num_frames); 
     y_v.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    y_v[counter].resize(num_frames); 
     z_v.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    z_v[counter].resize(num_frames); 
     my_container = VECTOR; 
     } 
     catch(std::bad_alloc &e) 
     { 
     x_d.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    x_d[counter].resize(num_frames); 
     y_d.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    y_d[counter].resize(num_frames); 
     z_d.resize(num_atoms); 
for (unsigned int couter=0;couter < num_frames; counter++) 
    z_d[counter].resize(num_frames); 
     my_container = DEQUE; 
     } 
     break; 
    } 
    } 
}; 

ora voglio essere in grado di definire i miei operatori staffa in modo che io possa avere una dichiarazione come x[1][2] accedere direttamente a seconda di quale è il contenitore di memoria reale sto usando (dato dal valore della mia variabile enumerata.

Ho visto un paio di esercitazioni che fluttuavano attorno all'override dell'operatore di parentesi, ma non ho idea di ignorare le doppie parentesi.

Come si possono sovraccaricare le doppie parentesi?

Inoltre, come si sovraccaricherà il doppio iteratore (nel caso in cui si desideri utilizzare un iteratore, al contrario dell'indicizzazione diretta)?

EDIT 1:

basato sulla soluzione da Martin York/Matteo Italia ho ideato il seguente classe:

template<typename T> 
class VectorDeque2D 
{ 
public: 

    class VectorDeque2D_Inner_Set 
    { 
    VectorDeque2D& parent; 
    int first_index; 
    public: 
    // Just init the temp object 
    VectorDeque2D_Inner_Set(My2D& p, int first_Index) : 
     parent(p), 
     first_Index(first_index) {} 
    // Here we get the value. 
    T& operator[](int second_index) const 
    { return parent.get(first_index,second_index);} 
    }; 

    // Return an object that defines its own operator[] that will access the data. 
    // The temp object is very trivial and just allows access to the data via 
    // operator[] 
    VectorDeque2D_Inner_Set operator[](unsigned int first_index) { 
    return (*this, x); 
    } 


    void resize_first_index(unsigned int first_index) { 
    try { 
     my_vector.resize(first_index); 
     my_container = VECTOR; 
    } 
    catch(std::bad_alloc &e) { 
     my_deque.resize(first_index); 
     my_container = DEQUE; 
    } 
    } 

    void resize_second_index(unsigned int second_index) { 
    try { 
     for (unsigned int couter=0;couter < my_vector.size(); counter++) { 
    my_vector[counter].resize(second_index); 
     } 
     my_container = VECTOR; 
    } 
    catch(std::bad_alloc &e) { 
     for (unsigned int couter=0;couter < my_deque.size(); counter++) { 
    my_deque[counter].resize(second_index); 
     } 
     my_container = DEQUE; 
    } 
    } 
    void resize(unsigned int first_index, 
      unsigned int second_index) { 
    try { 
     my_vector.resize(first_index); 
     for (unsigned int couter=0;couter < my_vector.size(); counter++) { 
    my_vector[counter].resize(second_index); 
     } 
     my_container = VECTOR; 
    } 
    catch(std::bad_alloc &e) { 
     my_deque.resize(first_index); 
     for (unsigned int couter=0;couter < my_deque.size(); counter++) { 
    my_deque[counter].resize(second_index); 
     } 
     my_container = DEQUE; 
    }  
    } 
private: 
    enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR }; 

    friend class VectorDeque2D_Inner_Set; 

    std::vector<std::vector<T> > my_vector; 
    std::deque<std::deque<T> > my_deque; 
    STORAGE_CONTAINER my_container; 

    T& get(int x,int y) { 
    T temp_val; 
    if(my_container == VECTOR) { 
     temp_val = my_vector[first_index][second_index]; 
    } 
    else if(my_container == DEQUE) { 
     temp_val = my_deque[first_index][second_index]; 
    } 

    return temp_val; 
    } 

}; 

Infine un contenitore di dimensioni 2D-safe !! Grazie ragazzi!

risposta

20

ci sono due tecniche principali:

1) Utilizzare operator() invece di operatore [].
Questo perché l'operatore() consente più parametri.

class My2D 
{ 
    public: 
     int& operator()(int x,int y) { return pget(x,y);} 
    private: 
     int& pget(int x,int y) { /* retrieve data from 2D storage */ } 
}; 

2) Utilizzare l'operatore [] ma restituire un oggetto intermedio.
È quindi possibile applicare il secondo operatore [] all'oggetto intermedio.

class My2D 
{ 
    public: 
     class My2DRow 
     { 
      My2D& parent; 
      int x; 
      public: 
       My2DRow(My2D& p, int theX) : parent(p), x(theX) {}  // Just init the temp object 
       int& operator[](int y) const { return parent.pget(x,y);} // Here we get the value. 
     }; 

     // Return an object that defines its own operator[] that will access the data. 
     // The temp object is very trivial and just allows access to the data via operator[] 
     My2DRow operator[](int x)  { return My2DRow(*this, x);} 
    private: 
     friend class My2DRow; 
     int& pget(int x,int y) { /* retrieve data from 2D storage */ } 
}; 

int main() 
{ 
    My2D data; 
    int& val = data[1][2]; // works fine. 

    // This is the same as 
    My2D::My2DRow row = data[1]; 
    int&   val2 = row[2]; 
} 

Preferisco la seconda tecnica.
Questo perché lascia intatto il codice originale e più naturale da leggere (in un contesto di matrice). Ovviamente si paga per la semplicità ad alto livello con un codice leggermente più complesso che implementa il proprio array 2D.

+2

Bello! Di nuovo, ciò che tu e Matteo avete offerto è quello che considererei una "vera" soluzione. Le persone che si lamentavano che non c'era modo di farlo erano semplicemente appese al fatto che non c'era un operatore singolare [] [] da sovraccaricare .... –

4
Come si può sovraccaricare le doppie parentesi?

Non ho capito completamente la tua domanda, ma devi sovraccaricare le parentesi e farle restituire un oggetto che sovraccarica il proprio operatore di parentesi.

Ad esempio, se si dispone di un vettore di vettori, il lavoro è già stato eseguito: sovraccarichi vector < vector < something > >operator[], che restituisce un vector< something >; questo, a sua volta, ha il suo operatore staffa sovraccaricato (e restituisce un oggetto something), in modo da poter semplicemente fare:

vector<vector<something> > vec; 
// ... 
something s = vec[2][3]; 


Esempio con un oggetto proxy:

template <typename T> 
class Container 
{ 
private: 
    // ... 


public: 

    // Proxy object used to provide the second brackets 
    template <typename T> 
    class OperatorBracketHelper 
    { 
     Container<T> & parent; 
     size_t firstIndex; 
    public: 
     OperatorBracketHelper(Container<T> & Parent, size_t FirstIndex) : parent(Parent), firstIndex(FirstIndex) {} 

     // This is the method called for the "second brackets" 
     T & operator[](size_t SecondIndex) 
     { 
      // Call the parent GetElement method which will actually retrieve the element 
      return parent.GetElement(firstIndex, SecondIndex); 
     } 

    } 

    // This is the method called for the "first brackets" 
    OperatorBracketHelper<T> operator[](size_t FirstIndex) 
    { 
     // Return a proxy object that "knows" to which container it has to ask the element 
     // and which is the first index (specified in this call) 
     return OperatorBracketHelper<T>(*this, FirstIndex); 
    } 

    T & GetElement(size_t FirstIndex, size_t SecondIndex) 
    { 
     // Here the actual element retrieval is done 
     // ... 
    } 
} 

(aggiunge sovraccarico metodi const, ove opportuno :))

Si noti che con questo metodo si perde quasi nulla rispetto all'implementazione di operator(), poiché il recupero viene comunque eseguito in un unico punto, senza co i limiti sull'utilizzo dei due indici, con entrambi gli indici al momento dell'esecuzione del recupero e senza restituire oggetti temporanei "grassi" (OperatorBracketHelper sono uguali a due puntatori e possono essere facilmente ottimizzati dal compilatore).

+0

Desidero poter fare riferimento ai dati contenuti nel mio tipo VectorDeque utilizzando doppie parentesi. Il tuo esempio funzionerebbe diversamente da quello che sto cercando di fare come non sto cercando di definire [] per VectorDeque, sto cercando di definire [] [] per VectorDeque, perché la mia dichiarazione è semplicemente VectorDeque(); not VectorDeque

+2

Beh, era solo un esempio. Tieni presente che * non esiste un operatore [] [] *, esiste solo l'operatore [] che può restituire un tipo che esegue nuovamente il overload dell'operatore []. In questo caso, è necessario creare un oggetto proxy temporaneo da restituire nell'operatore VectorDeque [], che manterrebbe un riferimento al suo oggetto padre e chiederà il risultato "corretto" quando viene chiamato il suo operatore []. Proverò ad aggiungere un esempio in un minuto. –

+0

Ah quindi c'è un modo, non solo tramite sovraccarico diretto ... –

1

Non sovraccaricare l'operatore [], sovraccaricare l'operatore ().

Vedere questo link: Overloading Subscript Operator.

consiglio vivamente la lettura attraverso il C++ FAQ Lite almeno una volta prima di pubblicare Stack Overflow. Inoltre, la ricerca di Overflow dello stack può fornire anche alcune informazioni utili.

+0

+1 per usare().Spesso viene trascurato quando si implementa l'indicizzazione multidimensionale. (Dio sa che l'ho fatto più di un paio di volte ... :) – Macke

+0

Scremato su quella sezione e la sua soluzione. Menziona che puoi usare [] []. Leggo il suo ragionamento sul perché di solito non lo vuoi, ma voglio preservare il carattere vettoriale-ish/deque-ish di esso, quindi penso che in questo caso valga gli aspetti negativi. Ma come lo faresti? Lui non offre molti dettagli. Sto pensando che ho bisogno di classi nidificate o qualcosa del genere, ma come faccio a preservare la gestione try/catch in una struttura così nidificata? –

+2

@Jason R. Mick: Come per molte cose nelle FAQ del C++, è molto unilaterale e giudicante e non sono d'accordo. Fornire la possibilità di usare [] [] per rendere leggibile il codice è il modo in cui consiglierei di farlo perché rende il codice più facile da leggere e quindi mantenere. (Al costo di rendere la tua classe matrix leggermente più difficile da leggere). Di seguito fornisco un semplice modello su come farlo senza esporre i dettagli di implementazione e quindi fornire flessibilità (PS. Questo è un modello relativamente standard (non qualcosa che ho inventato)). –

2

Non esiste un operatore "doppie parentesi" in C++. Quello che devi fare è definire un singolo operatore [] e farlo restituire un riferimento a un altro oggetto, che a sua volta può rispondere al proprio operatore []. Questo può essere annidato nel modo più approfondito possibile.

Ad esempio, quando si crea un vettore di vettori, l'operatore [] sul vettore esterno restituisce un riferimento a uno dei vettori interni; l'operatore [] su quel vettore restituisce un riferimento a un singolo elemento del vettore.

std::vector<std::vector<float> > example; 
std::vector<float> & first = example[0]; // the first level returns a reference to a vector 
float & second = example[0][0]; // the same as first[0] 
+0

Vedere l'esempio di Matteo di seguito per una soluzione ... –

+1

@Jason, non penso che tu abbia capito la mia risposta. Non ho detto che non potevi farlo; Ho iniziato dicendo che la risposta più ovvia era il modo sbagliato di pensare alla domanda, quindi ho descritto il modo corretto per ottenere quello che vuoi. Pensavo che la mia spiegazione fosse abbastanza semplice e diretta, per favore dimmi dove ho fallito. –

+0

Beh, sembra che tu abbia riattaccato dicendo che non potresti sovraccaricare [] [] perché non era un vero operatore. Non mi importava di sovraccaricare [] [], tanto quanto volevo essere in grado di indicizzare le cose usando [] [] per i tipi personalizzati, senza nidificazione. Martin York e Matteo Italia hanno fornito questo. Le tue risposte sono tecnicamente corrette alla "T", ma devi pensare fuori dagli schemi un po 'di più; in tal caso le tue risposte sarebbero più utili. –

0

Ho coperto l'operatore di sovraccarico [] per un array multidimensionale in un answer to a previous question.

Probabilmente avrei a che fare con gli iteratori in modo abbastanza simile: avere un iteratore che rappresenta una "sezione" (riga o colonna) della matrice multidimensionale e quindi un'altra che rappresenta un elemento in quella sezione.

+0

La tua risposta sembra venire da qui: http: // www. parashift.com/c++-faq-lite/operator-overloading.html#faq-13.10 Tuttavia, questo non risponde alla mia domanda. C'è un modo per usare [] []. Accetterò una risposta che trasforma la mia classe in una nidificata. Tuttavia, su un malloc male, tutte le istanze di VectorDeque devono essere modificate in Deque, non solo in quella .... –

+1

@Jason: In realtà, no. La risposta nella FAQ proviene da vecchi post Usenet, alcuni dei quali da me. Ad esempio, Robert Martin e io forniamo le due risposte in una discussione del 1996, che ritengo sia anteriore alle FAQ: http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/ filetto/0a52d34d173dd27d/298b8a42e13f4986 –