2009-04-16 9 views
7

Ho una classe X, che fornisco un frammento di qui:Concatenazione C++ iteratore spazia in una variabile membro vettore const in fase di costruzione

class X { 
    public: 
    template <typename Iter> 
    X(Iter begin, Iter end) : mVec(begin, end) {} 

    private: 
    vector<Y> const mVec; 
}; 

Ora voglio aggiungere un nuovo costruttore di concatenare a questa classe, qualcosa di simile:

template <typename Iter1, typename Iter2> 
X(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) : mVec(???) { ??? } 

Tale un costruttore sarebbe catenate le due catene [begin1, END1) e [begin2, FINE2) in mVec. Le sfide sono

1) vorrei preservare il const su mVec, in modo che sia considerato costante durante gli altri metodi di X.

2) Vorrei evitare copie non necessarie, se possibile. Cioè, una soluzione è quella di avere un metodo statico che costruisce un non-const temporanea per variare 1, inserti variano 2 e lo restituisce, e quindi definire il costruttore concatenazione di

template <typename Iter1, typename Iter2> 
X(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) 
    : mVec(concatenate(begin1, end1, begin2, end2)) { } 

ma che copia tutti i valori a almeno una volta in più, credo.

risposta

9

Bel problema. Vorrei provare a implementare un particolare tipo di wrapper iteratore che trasforma i due intervalli in un singolo intervallo. Qualcosa nelle linee di:

// compacted syntax for brevity... 
template <typename T1, typename T2> 
struct concat_iterator 
{ 
public: 
    typedef std::forward_iterator_tag iterator_category; 
    typedef typename iterator_traits<T1>::value_type value_type; 
    typedef *value_type pointer; 
    typedef &value_type reference; 

    concat_iterator(T1 b1, T1 e1, T2 b2, T2 e2) 
     : seq1(b1), seq1end(e1), seq2(b2), seq2end(e2); 
    iterator& operator++() { 
     if (seq1 != seq1end) ++seq1; 
     else ++seq2; 
     return this; 
    } 
    reference operator*() { 
     if (seq1 != seq1end) return *seq1; 
     else return *seq2; 
    } 
    pointer operator->() { 
     if (seq1 != seq1end) return &(*seq1); 
     else return &(*seq2); 
    } 
    bool operator==(concat_iterator const & rhs) { 
     return seq1==rhs.seq1 && seq1end==rhs.seq2 
      && seq2==rhs.seq2 && seq2end==rhs.seq2end; 
    } 
    bool operator!=(contact_iterator const & rhs) { 
     return !(*this == rhs); 
    } 
private: 
    T1 seq1; 
    T1 seq1end; 
    T2 seq2; 
    T2 seq2end; 
}; 

template <typename T1, typename T2> 
concat_iterator<T1,T2> concat_begin(T1 b1, T1 e1, T2 b2, T2 e2) 
{ 
    return concat_iterator<T1,T2>(b1,e1,b2,e2); 
} 
template <typename T1, typename T2> 
concat_iterator<T1,T2> concat_end(T1 b1, T1 e1, T2 b2, T2 e2) 
{ 
    return concat_iterator<T1,T2>(e1,e1,e2,e2); 
} 

Ora è possibile utilizzare:

class X { 
public: 
    template <typename Iter, typename Iter2> 
    X(Iter b1, Iter e1, Iter2 b2, Iter2 e2) 
     : mVec(concat_begin(b1,e1,b2,e2), concat_end(b1,e1,b2,e2)) 
    {} 

    private: 
    vector<Y> const mVec; 
}; 

o (ho solo pensato di essa) non è necessario per la vostra ridichiarare costruttore. Fai la tua chiamante utilizzare le funzioni di supporto:

X x(concat_begin(b1,e1,b2,e2), concat_end(b1,e1,b2,e2)); 

non ho controllato il codice, appena digitato qui fuori dalla parte superiore della mia testa. Potrebbe compilare o non potrebbe, potrebbe funzionare o no ... ma puoi prenderlo come punto di partenza.

+0

+1 per una soluzione intelligente ... un iteratore che si estende su due gamme di iteratori ... bello – veefu

+0

Sarebbe più facile scrivere usando boost :: iterator_facade, ma probabilmente è la soluzione migliore in questo caso. Almeno, finché non avremo il supporto per il trasloco. – Macke

+0

Non vedo la necessità di concat_end. È possibile confrontare l'iteratore generato con il tipo T2 del valore e2 ang generare un bool. –

2

Probabilmente sarebbe meglio lasciar cadere const (perché dovresti insistere comunque?).

In caso contrario, è necessario creare un iteratore concatenante. È un bel po 'di codice, vedi this thread per altro.

+0

Nel mio caso, la variabile membro vettoriale non dovrebbe cambiare dopo che l'istanza è stata costruita. Rendendolo const aiuta il compilatore ad aiutarmi a garantirlo. – SCFrench

+0

Bene, data la quantità di codice necessaria per fare la concatenazione, è più probabile che il tuo codice abbia un bug se si mantiene il const. – avakar

+0

SCFrench, non è abbastanza sicuro che X: mvec non cambierà dopo che è stata costruita la X? – veefu

2

Una delle funzionalità migliori o peggiori di C++, a seconda del punto di vista, è che è possibile utilizzarlo quando necessario per completare il lavoro. In questo caso, const_cast è la vittima:

template <typename Iter1, typename Iter2> 
X(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) : mVec(begin1, end1) { 
    const_cast<vector<Y>&>(mVec).insert(mVec.end(), begin2, end2); 
} 

mi potrebbe avere alcuni dei dettagli sbagliato, non ho provato a compilare questo. Ma dovrebbe darti l'idea.

+0

Stavo lavorando su un wrapper iteratore piuttosto complesso che avrebbe preso entrambi gli intervalli ... questo è molto più semplice, anche se ... const_cast ouch! –

+0

Questo è in realtà un comportamento non definito. Non dovresti rimuovere const dagli oggetti che sono stati definiti const. Tuttavia, è possibile rimuovere const dai riferimenti const associati a un oggetto non const. – avakar

+1

Undefined non sempre significa imprevedibile. Come ho detto, è decisamente un abuso. –

1

Il metodo statico potrebbe non essere così male come si pensa, a seconda dell'ottimizzazione eseguita dal compilatore. E in C++ 0x, i costruttori di movimento rimuoveranno qualsiasi copia attualmente in atto.

Nel frattempo andare con un iteratore wrapper. È improbabile che il codice sia tanto pericoloso quanto i collegamenti di thread thread, dal momento che è necessario implementare solo uno input iterator.

1

1) Vorrei conservare il const su mVec, in modo che sia considerato costante in tutti gli altri metodi di X.

  • Questo è un uso curioso di const su una variabile membro. E sfida il buon design. Per definizione, la costruzione è un processo che richiede che l'oggetto cambi.

  • Per quanto riguarda la necessità di mantenere l'oggetto non modificabile, utilizzare l'incapsulamento corretto. Dovresti usare le funzioni const -member per esporre qualsiasi funzionalità basata sul tuo mVec per i client della tua classe.

2) Vorrei evitare copie non necessarie se possibile. Cioè, una soluzione è di avere un metodo statico che costruisca un temporaneo non-const nell'intervallo 1, inserisce l'intervallo 2 e lo restituisce, quindi definisce il concatenatore costruttore su

Si dovrebbe guardare muovere-costruttori e r -valori di riferimento in generale (un obiettivo promesso di C++ 0x). Leggi questo article.

+0

Non capisco il tuo primo proiettile. Nel mio caso, una volta creato l'oggetto, il vettore dovrebbe rimanere invariato. Non lo segnalerebbe const rinforzarlo? Quale sarebbe un uso non curioso di const su una variabile membro? – SCFrench

+0

Non ho visto molto uso di una variabile membro 'const' quando non era statica. Quello che vuoi è un membro di sola lettura e non c'è alcun costrutto in C++ per farlo nel modo che desideri. La soluzione migliore è utilizzare gli accessors const anziché invocare UB tramite la modifica dovuta a const_casts. – dirkgently

Problemi correlati