2014-12-20 5 views
6

Ho una classe C++ che funge da contenitore: ha le funzioni membro size() e operator[]. I valori memorizzati "nel" contenitore sono oggetti std::tuple. Tuttavia, il contenitore non contiene effettivamente le tuple in memoria; invece, li costruisce on-demand in base ai dati sottostanti memorizzati in una forma diversa.Come implementare operator-> per un iteratore che costruisce i suoi valori su richiesta?

std::tuple<int, int, int> 
MyContainer::operator[](std::size_t n) const { 
    // Example: draw corresponding elements from parallel arrays 
    return { underlying_data_a[n], underlying_data_b[n], underlying_data_c[n] }; 
} 

Quindi, il tipo di ritorno di operator[] è un oggetto temporaneo, non un riferimento. (Ciò significa che non è un lvalue, quindi il contenitore è di sola lettura, che è OK.)

Ora sto scrivendo una classe iteratore che può essere utilizzata per attraversare le tuple in questo contenitore. Mi piacerebbe modellare RandomAccessIterator, che dipende da InputIterator, ma InputIterator richiede il supporto per l'espressione i->m (dove i è un'istanza di iteratore) e, per quanto posso dire, è necessaria una funzione operator-> per restituire un puntatore.

Naturalmente, non posso restituire un puntatore a una tupla temporanea costruita su richiesta. Una possibilità che viene in mente è quello di mettere un'istanza tupla in iteratore come variabile membro, e usarlo per memorizzare una copia di qualsiasi valore dell'iteratore è attualmente posizionato su:

class Iterator { 
private: 
    MyContainer *container; 
    std::size_t current_index; 

    // Copy of (*container)[current_index] 
    std::tuple<int, int, int> current_value; 
    // ... 
}; 

Tuttavia, l'aggiornamento del valore memorizzato richiederà l'iteratore per verificare se il suo indice corrente è inferiore alla dimensione del contenitore, in modo che un iteratore passato -end-end non causi un comportamento indefinito accedendo oltre la fine degli array sottostanti. Ciò aggiunge (una piccola quantità di) sovraccarico di runtime - non abbastanza per rendere la soluzione poco pratica, ovviamente, ma si sente un po 'poco elegante. L'iteratore non dovrebbe davvero essere necessario memorizzare altro che un puntatore al contenitore che sta iterando e la posizione corrente al suo interno.

Esiste un modo pulito e consolidato per supportare operator-> per i tipi di iteratore che costruiscono i loro valori su richiesta? Come altri sviluppatori farebbero questo genere di cose?

(Si noti che non si ha realmente bisogno per sostenere operator-> a tutti - sto implementando l'iteratore soprattutto in modo che il contenitore può essere attraversato con un C++ 11 "range for" loop, e std::tuple doesn' t ho membri che normalmente vorremmo accedere via ->. Ma mi piacerebbe modellare i concetti di iteratore correttamente, tuttavia, mi sento come se stessi tagliando altrimenti altrimenti non dovrei preoccuparmi?)

+0

'operator->' non deve restituire un puntatore. Deve solo restituire qualcosa su cui '*' può essere applicato. –

+0

Hmm, quindi potrei restituire un "puntatore falso" che contiene un valore e restituisce quel valore quando "dereferenziato"? Non sono sicuro se sia più o meno imbarazzante che memorizzare il valore nell'iteratore stesso. :-) Ma almeno (con inlining) ci sarebbe zero overhead di run-time. – Wyzard

+1

In ogni caso, per modellare 'ForwardIterator' è necessario soddisfare questo requisito:" Se aeb sono entrambi dereferenziabili, quindi a == b se e solo se * a e * b sono legati allo stesso oggetto. " Ciò significa in genere che gli iteratori che restituiscono un proxy o che contengono il valore dentro di sé possono essere solo InputIterators –

risposta

1

Ecco un esempio che si basa sul fatto che operator-> viene applicato ripetutamente fino a quando viene restituito un puntatore. Facciamo Iterator::operator-> restituire l'oggetto contenuto come temporaneo. Ciò fa sì che il compilatore riapplichi operator->. Facciamo quindi Contained::operator-> semplicemente restituendo un puntatore a se stesso. Notare che se non si desidera inserire operator-> nell'oggetto Contiene al volo, possiamo avvolgerlo in un oggetto helper che restituisce un puntatore all'oggetto Interno contenuto.

#include <cstddef> 
#include <iostream> 

class Contained { 
    public: 
     Contained(int a_, int b_) : a(a_), b(b_) {} 
     const Contained *operator->() { 
      return this; 
     } 
     const int a, b; 
}; 

class MyContainer { 
    public: 
     class Iterator { 
       friend class MyContainer; 
      public: 
       friend bool operator!=(const Iterator &it1, const Iterator &it2) { 
        return it1.current_index != it2.current_index; 
       } 
      private: 
       Iterator(const MyContainer *c, std::size_t ind) : container(c), current_index(ind) {} 
      public: 
       Iterator &operator++() { 
        ++current_index; 
        return *this; 
       } 
       // -> is reapplied, since this returns a non-pointer. 
       Contained operator->() { 
        return Contained(container->underlying_data_a[current_index], container->underlying_data_b[current_index]); 
       } 
       Contained operator*() { 
        return Contained(container->underlying_data_a[current_index], container->underlying_data_b[current_index]); 
       } 
      private: 
       const MyContainer *const container; 
       std::size_t current_index; 
     }; 
    public: 
     MyContainer() { 
      for (int i = 0; i < 10; i++) { 
       underlying_data_a[i] = underlying_data_b[i] = i; 
      } 
     } 
     Iterator begin() const { 
      return Iterator(this, 0); 
     } 
     Iterator end() const { 
      return Iterator(this, 10); 
     } 
    private: 
     int underlying_data_a[10]; 
     int underlying_data_b[10]; 
}; 

int 
main() { 
    MyContainer c; 

    for (const auto &e : c) { 
     std::cout << e.a << ", " << e.b << std::endl; 
    } 
} 
+2

Ovviamente, fai attenzione: qualcosa come 'const int & a = it-> a;' normalmente funzionerebbe, ma non sarà qui, poiché l'oggetto temporaneo sarà già distrutto dal momento in cui 'a' viene usato. – hvd

+0

Qualsiasi motivo particolare per cui 'Contenuto 'contiene i singoli valori direttamente, al contrario di tenere una' std :: tuple' (e restituire il suo indirizzo da 'operator->')? – Wyzard

+0

@Wyzard: è possibile utilizzare una tupla, ma in questo caso è necessario eseguire il wrapping, poiché std :: tuple non ha -> sovraccarico per questo. – kec

2
template<class T> 
struct pseudo_ptr { 
    T t; 
    T operator*()&&{return t;} 
    T* operator->(){ return &t; } 
}; 

poi

struct bar { int x,y; }; 
struct bar_iterator:std::iterator< blah, blah >{ 
    // ... 
    pseudo_ptr<bar> operator->() const { return {**this}; } 
    // ... 
}; 

Questo si basa su come -> opere.

ptr->b per il puntatore ptr è semplicemente (*ptr).b.

In caso contrario, è definito come (ptr.operator->())->b. Questo viene valutato in modo ricorsivo se operator-> non restituisce un puntatore.

Il pseudo_ptr<T> in alto offre un involucro attorno a una copia di T.

Nota, tuttavia, che l'estensione della durata non funziona. Il risultato è fragile.

Problemi correlati