vecteur et const
Considérez ceci
void f(vector<const T*>& p)
{
}
int main()
{
vector<T*> nonConstVec;
f(nonConstVec);
}
Ce qui suit ne compile pas.La chose est que vector<T*>
ne peut pas être converti en vector <const T*>
, et cela me semble illogique, car il existe une conversion implicite de T*
à const T*
. Pourquoi est-ce ?
vector<const T*>
ne peut pas être convertie vector <T*>
, mais c'est normal car const T*
ne peut pas être converti implicitement à T*
.
10 réponses
J'ai ajouté quelques lignes à votre code. C'est suffisant pour expliquer pourquoi cela est refusé:
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();
}
vector<T>
et vector<const T>
sont des types non liés. Le fait que T
puisse être converti en const T
ne signifie rien ici.
Vous devez y penser du point de vue d'un système de type. Instancié vector<int>
n'a rien en commun avec vector<const int>
.
Il peut être utile de montrer pourquoi c'est une violation de const-correctif pour effectuer la conversion que vous voulez:
#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.
}
Le problème est que vector<int*>.push_back
prend un pointeur vers non-const (que j'appellerai un "pointeur non-const" à partir de Maintenant). Cela signifie qu'il pourrait modifier le pointee de son paramètre. Plus précisément dans le cas de vector, il pourrait remettre le pointeur à quelqu'un d'autre qui le modifie. Donc, vous ne pouvez pas passer un pointeur const à la fonction push_back de w, et la conversion que vous voulez est dangereux même si le système de modèle le supportait (ce qui n'est pas le cas). Le but de const-safety est de vous empêcher de passer un pointeur const à une fonction qui prend un pointeur non-const, et c'est ainsi qu'il fait son travail. C++ vous oblige à dire spécifiquement si vous voulez faire quelque chose de dangereux, donc la conversion ne peut certainement pas être implicite. En fait, en raison du fonctionnement des modèles, ce n'est pas possible du tout (voir plus loin).
Je pense que C++ pourrait en principe préserver la sécurité const en permettant un la conversion à partir de vector<T*>&
à const vector<const T*>&
, comme int **
à const int *const *
est sûr. Mais c'est à cause de la façon dont vector est défini: il ne serait pas nécessairement const-safe pour d'autres modèles.
De même, il pourrait en théorie permettre une conversion explicite. Et en fait, il permet une conversion explicite, mais seulement pour les objets, pas les références ; -)
std::vector<const int*> x(w.begin(), w.end()); // conversion
La raison pour laquelle il ne peut pas le faire pour les références est parce que le système de modèle ne peut pas le supporter. Un autre exemple qui serait cassé si le conversion ont été autorisés:
template<typename T>
struct Foo {
void Bar(T &);
};
template<>
struct Foo<const int *> {
void Baz(int *);
};
Maintenant, Foo<int*>
n'a pas de fonction Baz. Comment diable un pointeur ou une référence à Foo<int*>
pourrait-il être converti en un pointeur ou une référence à 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?
Pensez comme ceci:
Vous avez deux classes comme ceci:
class V { T* t;};
class VC { T const* t;};
Attendez-vous que ces deux classes soient convertibles automatiquement?
C'est essentiellement ce qu'est une classe de modèle. Chaque variation est un type complètement nouveau.
Ainsi vector
Ma première question Est voulez-vous vraiment stocker des pointeurs?
Si oui, je suggère de regarder boost:: ptr_container. Cela contient des pointeurs et les supprime lorsque le vecteur est détruit. Mais plus important encore, il traite les pointeurs contenus comme un std normal: vector traite ses objets contenus. Ainsi, en faisant le vecteur const vous ne pouvez accéder à ses membres que 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.
}
Si vous n'avez pas besoin de stocker des pointeurs, stockez les objets (pas les pointeurs) dans le conteneur.
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.
}
D'autres ont déjà donné la raison pour laquelle le code que vous avez donné ne compile pas, mais j'ai une réponse différente sur la façon de le gérer. Je ne crois pas qu'il existe un moyen d'enseigner au compilateur comment convertir automatiquement les deux (car cela impliquerait de changer la définition de std::vector
). La seule façon de contourner cet ennui est de faire une conversion explicite.
La conversion en un vecteur complètement différent n'est pas satisfaisante (gaspille de la mémoire et des cycles pour quelque chose qui devrait être complètement identique). Je suggère ce qui suit:
#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));
}
J'utilise reinterpret_cast
pour déclarer que les deux choses sont les mêmes. Vous devriez vous sentir sale après l'avoir utilisé, mais si vous le mettez dans une fonction par elle-même avec un commentaire pour ceux qui vous suivent, alors lavez-vous et essayez de continuer votre chemin avec une bonne conscience, bien que vous ayez toujours (à juste titre) cette inquiétude lancinante au sujet de quelqu'un tirant le sol sous vous.
Comme d'autres l'ont dit, les conversions ne sont pas appliquées aux paramètres du modèle. Mettre une autre manière,
vector<T>
...et:
vector<const T>
... sont des types complètement différents.
Si vous essayez d'implémenter const-correctif en ce qui concerne f() Ne pas modifier le contenu du vecteur, cela pourrait être plus le long de ce que vous cherchez:
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());
}
En plus des autres réponses, il vaut la peine de lire C++ FQA Lite où ceci (et beaucoup d'autres fonctionnalités C++) sont discutés à partir d'un POV critique: http://yosefk.com/c++apq/const.html#fqa-18.1
Les deux vector<const T*>
et vector<T*>
sont des types complètement différents. Même si vous écrivez const T*
dans votre main()
, votre code ne compilera pas. Vous devez fournir une spécialisation à l'intérieur principal.
Les compilations suivantes:
#include<vector>
using namespace std;
template<typename T>
void f(vector<const T*>& p)
{
}
int main()
{
vector<const int*> nonConstVec;
f(nonConstVec);
}
Les modèles sont un peu étranges de cette façon. Le fait qu'il y ait une conversion implicite de T à U ne signifie pas qu'il y ait une conversion implicite de XXX à XXX. cela peut se produire, mais il faut beaucoup de travail supplémentaire dans le code du modèle pour que cela se produise, et désinvolte, je doute que les techniques étaient toutes connues quand std::vector
était en cours de conception (plus précisément, je suis sûr qu'elles n'étaient pas connues).
Edit: des problèmes comme celui-ci font partie de la motivation derrière l'utilisation d'itérateurs. Même si un container of X
n'est pas implicitement convertible en un container of const X
, un container<X>::iterator
est implicitement convertible en un container<X>::const_iterator
.
Si vous remplacez votre:
void f(vector<const T*>& p) {}
Avec:
template <class const_iter>
void f(const_iter b, const_iter e) {}
Puis:
int main() {
vector<T*> nonConstVec;
f(nonConstVec.begin(), nonConstVec.end());
return 0;
}
Sera très bien -- et il en sera de:
vector<T const *> constVec;
f(constVec.begin(), constVec.end());