2010-01-20 15 views
23

considerare questovettoriale e const

void f(vector<const T*>& p) 
{ 
} 
int main() 
{ 
    vector<T*> nonConstVec; 
    f(nonConstVec); 
} 

Quanto segue non compile.The cosa è che vector<T*> non può essere convertito in vector <const T*>, e che sembra illogicamente a me, perché esiste conversione implicita T*-const T*. Perchè è questo ?

vector<const T*> non possono essere convertiti in vector <T*> troppo, ma che ci si aspetta, perché const T* non può essere convertito implicitamente T*.

risposta

38

Ho aggiunto alcune righe al codice. Questo è sufficiente a chiarire il motivo per cui questo non è consentita:

void f(vector<const T*>& p) 
{ 
    static const T ct; 
    p.push_back(&ct); // adds a const T* to nonConstVec ! 
} 
int main() 
{ 
    vector<T*> nonConstVec; 
    f(nonConstVec); 
    nonConstVec.back()->nonConstFunction(); 
} 
+0

great, non ho mai pensato a questo –

+0

se questo fosse il motivo, la conversione in 'const vettore ' dovrebbe funzionare – marcin

+2

@marcin: T Hat è teoricamente sicuro, ma non c'è modo per il compilatore di determinarlo senza una conoscenza dettagliata di 'std :: vector'. Inoltre, per renderlo legale richiederebbe una modifica complessa alle regole della sequenza di conversione. Quali sono i requisiti per 'modello classe Foo' per un' Foo 'da trasmettere a' const Foo '? Ricorda, 'Foo' in questo contesto potrebbe essere un modello di puntatore intelligente o qualsiasi altra cosa, non necessariamente un contenitore. – MSalters

22

vector<T> e vector<const T> sono tipi non correlati. Il fatto che T possa essere convertito in const T non significa niente qui.

Devi pensarci dal punto di vista di un sistema di tipi. Istanziato vector<int> non ha nulla in comune con vector<const int>.

+1

Grande risposta. Questo mi ha chiarito esattamente * cosa * è illegale e perché, dal punto di vista del compilatore, non è permesso. Le altre risposte spiegano perché potrebbe essere una cattiva idea, che è bene sapere, ma un sacco di codice legale è una cattiva idea. – Praxeolitic

0

Ecco come funzionano i modelli: nessuna conversione viene applicata ai parametri del modello, quindi i due vettori sono di tipi completamente diversi.

+0

Di come passare il mio vettore alla funzione che ottiene il vettore ? Certamente non voglio cambiare la firma della funzione perché è come un commento che dice che non modificherà l'oggetto passato. – user152508

+0

Beh, dovrai cambiarlo, temo. Se vuoi dire che non stai cambiando il vettore, allora vuoi f (const vector &); –

+0

f (const &) significa che il vettore stesso (non contiene puntatori) non verrà modificato, ma gli oggetti puntati su puntatori possono – user152508

0

I modelli sono un po 'strani in questo modo. Il fatto che ci sia una conversione implicita da T a U non significa che ci sia una conversione implicita da XXX a XXX. Può essere fatto succedere, ma ci vuole una buona quantità di lavoro extra nel codice del template per farlo accadere, e in modo offensivo, dubito che le tecniche fossero tutte note quando è stato progettato std::vector (più precisamente, sono abbastanza sicuro che non erano conosciuti).

Modifica: problemi come questo sono parte della motivazione alla base dell'utilizzo degli iteratori. Anche se un container of X non è implicitamente convertibile in uno container of const X, uno container<X>::iteratorè implicitamente convertibile in uno container<X>::const_iterator.

Se si sostituisce il:

void f(vector<const T*>& p) {} 

con:

template <class const_iter> 
void f(const_iter b, const_iter e) {} 

Poi:

int main() { 
    vector<T*> nonConstVec; 
    f(nonConstVec.begin(), nonConstVec.end()); 
    return 0; 
} 

sarà bene - e così sarà:

vector<T const *> constVec; 
f(constVec.begin(), constVec.end()); 
0

Entrambi vector<const T*> e vector<T*> sono tipi completamente diversi. Anche se scrivi const T* all'interno del tuo main(), il tuo codice non verrà compilato. È necessario fornire la specializzazione all'interno principale.

i seguenti: compila

#include<vector> 
using namespace std; 

template<typename T> 
void f(vector<const T*>& p) 
{ 
} 
int main() 
{ 
    vector<const int*> nonConstVec; 
    f(nonConstVec); 
} 
3

Come altri hanno già detto, le conversioni non sono applicati ai parametri del modello. Detto in altro modo,

vector<T> 

...e:

vector<const T> 

... sono tipi completamente diversi.

Se si sta tentando di implementare const correttezza in materia di f() non modifica il contenuto del vettore, questo potrebbe essere più lungo le linee di quello che stai cercando:

void f(vector<T>::const_iterator begin, vector<T>::const_iterator end) 
{ 
    for(; begin != end; ++begin) 
    { 
    // do something with *begin 
    } 
} 

int main() 
{ 
    vector<T> nonConstVec; 
    f(nonConstVec.begin(), nonConstVec.end()); 
} 
6

Pensate di come questo:

avete due classe come questa:

class V { T*  t;}; 
class VC { T const* t;}; 

ti aspetti queste due classi per essere convertibile automaticamente?
Questo è fondamentalmente ciò che è una classe template. Ogni variazione è un tipo completamente nuovo.

Così vettore < T * > e vettoriale < T const * > sono completamente diversi tipi.

La mia prima domanda è: vuoi davvero memorizzare i puntatori?

Se sì, suggerirei di guardare boost :: ptr_container. Ciò mantiene i puntatori e li cancella quando il vettore viene distrutto. Ma ancora più importante tratta i puntatori contenuti come un normale std: il vettore tratta i suoi oggetti contenuti. Così facendo il const vettore è possibile accedere solo ai suoi membri come const

void function(boost::ptr_vector<T> const& x) 
{ 
    x.push_back(new T); // Fail x is const. 
    x[4].plop();   // Will only work if plop() is a const member method. 
} 

Se non è necessario per memorizzare i puntatori quindi memorizzare gli oggetti (non i puntatori) nel contenitore.

void function(std::vector<T> const& x) 
{ 
    x.push_back(T()); // Fail x is const. 
    x[4].plop();   // Will only work if plop() is a const member method. 
} 
13

Potrebbe valere la pena che mostra perché è una violazione della const correttezza per eseguire la conversione che si desidera:

#include <vector> 
const int a = 1; 

void addConst(std::vector<const int *> &v) { 
    v.push_back(&a); // this is OK, adding a const int* to a vector of same 
} 

int main() { 
    std::vector<int *> w; 
    int b = 2; 
    w.push_back(&b); // this is OK, adding an int* to a vector of same 
    *(w.back()) = 3; // this is OK, assigning through an int* 
    addConst(w);  // you want this to be OK, but it isn't... 
    *(w.back()) = 3; // ...because it would make this const-unsafe. 
} 

Il problema è che vector<int*>.push_back prende un pointer-to-non-const (che chiamerò un "puntatore non-const" da ora in poi). Ciò significa che potrebbe modificare il punto morto del suo parametro. Specificamente nel caso del vettore, potrebbe restituire il puntatore a qualcun altro che lo modifica. Quindi non è possibile passare un puntatore const alla funzione push_back di w e la conversione desiderata non è sicura anche se il sistema template lo supporta (cosa che non lo è). Lo scopo di const-safety è quello di interrompere il passaggio di un puntatore const a una funzione che accetta un puntatore non-const e in questo modo fa il suo lavoro. C++ richiede di dire in modo specifico se si vuole fare qualcosa di non sicuro, quindi la conversione certamente non può essere implicita. Infatti, a causa del modo in cui i template funzionano, non è possibile affatto (vedi più avanti).

ritengo C++ potrebbe, in linea di principio preservare const-sicurezza consentendo una conversione da vector<T*>& a const vector<const T*>&, come int ** per const int *const * è sicuro. Ma questo è dovuto al modo in cui viene definito il vettore: non sarebbe necessariamente cost-safe per altri template.

Allo stesso modo, in teoria potrebbe consentire una conversione esplicita.E infatti, se non è permessa una conversione esplicita, ma solo per gli oggetti, non i riferimenti ;-)

std::vector<const int*> x(w.begin(), w.end()); // conversion 

Il motivo per cui non può farlo per i riferimenti è perché il sistema di modello non può sostenerlo. Un altro esempio che potrebbe essere rotto se la conversione è stato permesso:

template<typename T> 
struct Foo { 
    void Bar(T &); 
}; 

template<> 
struct Foo<const int *> { 
    void Baz(int *); 
}; 

Ora, Foo<int*> non ha una funzione di Baz. In che modo un puntatore o un riferimento a Foo<int*> può essere convertito in un puntatore o riferimento a Foo<const int*>?

Foo<int *> f; 
Foo<const int *> &g = f; // Not allowed, but suppose it was 
int a; 
g.Baz(&a); // Um. What happens? Calls Baz on the object f? 
4

Altri hanno già dato il motivo per cui il codice che ha dato non viene compilato, ma ho una risposta diversa su come trattare con esso. Non credo che ci sia un modo per insegnare al compilatore come convertire automaticamente i due (perché ciò implicherebbe la modifica della definizione di std::vector). L'unico modo per aggirare questo fastidio è fare una conversione esplicita.

La conversione in un vettore completamente diverso non è soddisfacente (spreca memoria e cicli per qualcosa che dovrebbe essere completamente identico). Suggerisco la seguente:

#include <vector> 
#include <iostream> 

using namespace std; 

typedef int T; 

T a = 1; 
T b = 2; 

void f(vector<const T*>& p) 
{ 
    for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) { 
     cout << **iter << endl; 
    } 
} 
vector<const T*>& constify(vector<T*>& v) 
{ 
    // Compiler doesn't know how to automatically convert 
    // std::vector<T*> to std::vector<T const*> because the way 
    // the template system works means that in theory the two may 
    // be specialised differently. This is an explicit conversion. 
    return reinterpret_cast<vector<const T*>&>(v); 
} 
int main() 
{ 
    vector<T*> nonConstVec; 
    nonConstVec.push_back(&a); 
    nonConstVec.push_back(&b); 
    f(constify(nonConstVec)); 
} 

sto usando reinterpret_cast a dichiarare che le due cose sono le stesse. Tu DOVREI DOVREBBE sentirti sporco dopo averlo usato, ma se lo metti in una funzione da solo con un commento per quelli che ti seguono, quindi lavati e prova a continuare sulla tua strada con una buona coscienza, anche se lo farai sempre (giustamente) che ti preoccupi di qualcuno che tira fuori il terreno da te.

1

in aggiunta alle altre risposte, vale la pena di leggere C++ FQA Lite in cui questo (e molti altri C++ caratteristiche) sono discussi da un POV critica: http://yosefk.com/c++fqa/const.html#fqa-18.1

+0

Sono d'accordo che vale la pena leggerlo. Nonostante il suo stile aggressivo, FQA è un buon modo per consolidare la conoscenza del C++ – AKludges