OVH Cloud OVH Cloud

Pourquoi l'operateur -> const des smart ptr renvoie un non const ?

28 réponses
Avatar
Aurelien Regat-Barrel
Bonjour,
Je suis surpris et déçu de constater que plusieurs pointeurs
intelligents (auto_ptr, boost) ont leur opérateur d'indirection défini
ainsi:

T* operator->() const;

alors que je me serais attendu à:

T* operator->();
const T* operator->() const;

ce qui permet d'avoir une erreur de compilation dans l'exemple qui suit:

class A
{
public:
void f1() const {}
void f2() {}
};

const auto_ptr<A> p( new A ); // <- const
p->f1();
p->f2(); // erreur de compilation attendue mais...

Du coup il faut passer par un

auto_ptr<const A>

qui ne peut pas être implicitement converti depuis un

auto_ptr<A>

:/
(cela dit ça marche avec boost, dans une certaine mesure). Je supose
qu'il y a une bonne raison à celà, mais je ne la devine pas. La
connaissez-vous ?

Merci.

--
Aurélien Regat-Barrel

10 réponses

1 2 3
Avatar
Aurelien Regat-Barrel

Il y a des limites de ce qu'on peut faire avec des conversions
de type implicites. Est-ce que tu veux qu'on puisse convertir
tous les vecteurs, du moment que le type contenu dans un se
laisse convertir en type contenu dans l'autre. Que je puisse
affeecter un vector<double> à un vector<char>, c-à-d :

typedef std::vector< char > CVect ;
typedef std::vector< double > DVect ;

void Test( CVect ) ;

DVect dv ;
Test( dv ) ;

Note aussi que c'est assez rare de passer des vecteurs par
valeur. Mais si on passe par référence (const), attention au
dégat :

typedef std::vector< char > CVect ;
void Test( CVect const& ) ;

DVect dv ;
Test( dv ) ;


Après réflexion, je réalise que c'est au niveau des références qu'est le
problème. Ceci compile:

char a;
char const & b = a;

mais pas ceci:

char * a = 0;
char const * & b = a;

En fait, je ne comprends pas pourquoi.

A peu de chose près, j'imagine que c'est la même règle qui s'applique
pour le refus d'initialiser une référence sur un template<T const> à
partir d'un template<T>.

Du coup, j'ai une copie profonde que je n'ai peut-être pas
voulue. Ça coûte cher, copier tout un vecteur.

Note bien que si tu veux la copie, tu peux toujours écrire :

Test2( AConstVect( v.begin(), v.end() ) ) ;

C'est la façon à appliquer la conversion explicite sur chaque
élément du vecteur.


Je pense que je vais opter pour cette solution, avec un peu de chance le
compilateur fera une bonne optimisation.

du coup, j'hésite à utiliser const dans mes pointeurs
intelligents, et donc il perd son utilité au sein même de mes
classes :-/



Je ne sais pas. Dans l'ensemble, je crois que boost::shared_ptr
se comporte assez comme un pointeur brut à cet égard. Et j'avoue
que ces restrictions ne m'ont jamais causé de problèmes. Les
copies pûres et simples des vecteurs sont plutôt rares dans mon
code ; ce qui arrive souvent, en revanche, ce sont des
sous-ensembles -- une copie qui ne copie que certains éléments.
Et dans ce cas-là, la solution la plus simple, c'est un
itérateur filtrant de Boost, et le constructeur à deux
itérateurs du vecteur. Mais alors, si le sous-ensemble doit être
un vector< AConstPtr >, plutôt qu'un vector< APtr >, pas de
problème -- parce que je me sers du constructeur à deux
itérateurs, et que donc le compilateur cherche à faire la
conversion élément par élément, plutôt que sur le vecteur
complet.


--
Aurélien Regat-Barrel


Avatar
Falk Tannhäuser
Aurelien Regat-Barrel wrote:
Après réflexion, je réalise que c'est au niveau des référence s qu'est le
problème. Ceci compile:

char a;
char const & b = a;

mais pas ceci:

char * a = 0;
char const * & b = a;

En fait, je ne comprends pas pourquoi.


char const c[] = "Parce que cela permettrait de casser la 'const cor rectness' !";
b = c;
std::strcpy(a, "Et voilà :-p !");

Falk

Avatar
Aurelien Regat-Barrel

En fait, je ne comprends pas pourquoi.



char const c[] = "Parce que cela permettrait de casser la 'const
correctness' !";
b = c;
std::strcpy(a, "Et voilà :-p !");


Ok. Avec un autre const, ça marche:

char * a = 0;
char const * const & b = a;

Du coup, je m'attends à pouvoir écrire:

std::vector<char> a;
std::vector<char> const & b = a; // OK
std::vector<char const> const & c = a; // :-(

mais je commence à trop nager pour pouvoir évaluer tout ce que cela
implique.

--
Aurélien Regat-Barrel


Avatar
Falk Tannhäuser
Aurelien Regat-Barrel wrote:

Ok. Avec un autre const, ça marche:

char * a = 0;
char const * const & b = a;

Du coup, je m'attends à pouvoir écrire:

std::vector<char> a;
std::vector<char> const & b = a; // OK
std::vector<char const> const & c = a; // :-(


std::vector<char const> ne passe pas parce que
le type qu'on met dans un vector doit supporter
l'affectation.

En revanche, on peut imaginer des conversions qui seraient parfaitement
sûres mais qui ne sont néanmoins pas supportées par le langage (sans doute
personne ne les a pas proposées pour la standardisation parce que
les cas où elles seraient utiles se présentent trop rarement dans
la pratique). Par exemple
std::vector<char*>::iterator ==> std::vector<char const*>::const_iterator
std::vector<char>::iterator* ==> std::vector<char>::const_iterator const*
(Note: Ces deux conversions passeront la compilation sur les implémentations
où 'std::vector<T>::[[const_]]iterator' est défini comme 'T [[const]]*'. Ces
implémentations semblent toutefois devenir de plus en plus rares ces jours-ci.)

Similairement, il pourrait aussi y avoir des conversions entre pointeurs
sur les fonctions comme
char* (*)() ===> char const* (*)()
void (*)(char const*) ===> void (*)(char*)

Falk

Avatar
Fabien LE LEZ
On Fri, 28 Apr 2006 14:24:00 +0200, Fabien LE LEZ
:

class Machin
{
public:
void truc();


void bidule() const;


class HandleMachin
{
public:


void bidule() const { assert(EstOk()); ptr->bidule(); }

Avatar
James Kanze
Fabien LE LEZ wrote:
On Thu, 27 Apr 2006 22:03:36 +0200, Fabien LE LEZ
:


Tu peux faire une classe qui se comporte comme tu le souhaites.
L'appeler "pointeur" me choquerait effectivement.



En fait, je m'aperçois que je fais assez souvent des classes (non
templates) de ce style, et que je les appelle généralement "handle",
"gestionnaire", ou quelque chose d'approchant. Mais ces classes n'ont
pas d'opérateur -> ou * : elles ont grosso modo les mêmes fonctions
que la classe "de base".


Le modèle façade, en somme.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
Jean-Marc Bourguet
James Kanze writes:

Fabien LE LEZ wrote:
On Thu, 27 Apr 2006 22:03:36 +0200, Fabien LE LEZ
:

Tu peux faire une classe qui se comporte comme tu le souhaites.
L'appeler "pointeur" me choquerait effectivement.


En fait, je m'aperçois que je fais assez souvent des classes (non
templates) de ce style, et que je les appelle généralement "handle",
"gestionnaire", ou quelque chose d'approchant. Mais ces classes n'ont
pas d'opérateur -> ou * : elles ont grosso modo les mêmes fonctions
que la classe "de base".


Le modèle façade, en somme.


Proxy plutôt?

Il me semblait que le modèle façade, c'est proposer une
interface unifiée à un ensemble de classes.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org



Avatar
James Kanze
Aurelien Regat-Barrel wrote:

[...]
Après réflexion, je réalise que c'est au niveau des références
qu'est le problème. Ceci compile:


char a;
char const & b = a;


mais pas ceci:


char * a = 0;
char const * & b = a;


En fait, je ne comprends pas pourquoi.


Parce que :

char * a = 0 ;
char const* & b = a ;
char const c = 'x' ;
b = &c ;

Et où est-ce que « a » pointe maintenant ?

A peu de chose près, j'imagine que c'est la même règle qui
s'applique pour le refus d'initialiser une référence sur un
template<T const> à partir d'un template<T>.


Pas tout à fait. T et T const sont deux types distincts.
Apparenté, bien sûr, mais quand même des types différents. Ce
qui fait qu'ils donnent lieu à des instances de template
distinctes. Seulement, il n'y a pas d'apparenté entre les
différentes instances d'un template, même s'il y en a entre
leurs paramètres.

Dans la pratique, je ne vois pas comment ça pourrait ne pas être
le cas. C'est facile de trouver des cas particuliers où ça
serait commode si, mais essaie de formuler une règle générale et
absolue qui s'appliquerait à tous les templates.

Du coup, j'ai une copie profonde que je n'ai peut-être pas
voulue. Ça coûte cher, copier tout un vecteur.



Note bien que si tu veux la copie, tu peux toujours écrire :



Test2( AConstVect( v.begin(), v.end() ) ) ;



C'est la façon à appliquer la conversion explicite sur chaque
élément du vecteur.



Je pense que je vais opter pour cette solution, avec un peu de
chance le compilateur fera une bonne optimisation.


Disons que c'est l'inverse. Je ne sais pas à quelle optimisation
tu t'attends, mais déjà avec la génération d'un temporaire, tu
as (ou aurais) une copie profonde. Cette forme ne serait pas
pire.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
Jean-Marc Bourguet wrote:
James Kanze writes:


Fabien LE LEZ wrote:
On Thu, 27 Apr 2006 22:03:36 +0200, Fabien LE LEZ
:




Tu peux faire une classe qui se comporte comme tu le
souhaites. L'appeler "pointeur" me choquerait
effectivement.





En fait, je m'aperçois que je fais assez souvent des
classes (non templates) de ce style, et que je les appelle
généralement "handle", "gestionnaire", ou quelque chose
d'approchant. Mais ces classes n'ont pas d'opérateur ->
ou * : elles ont grosso modo les mêmes fonctions que la
classe "de base".




Le modèle façade, en somme.



Proxy plutôt?


Il me semblait que le modèle façade, c'est proposer une
interface unifiée à un ensemble de classes.


Intéressant. C'est la deuxième fois dernièrement où quelqu'un a
dit proxy où j'aurais dit façade.

Dans ma tête, proxy, c'était toujours l'idiome du proxy que je
connaissais bien avant le GoF : celui de l'item 30 dans « More
Effective C++ », par exemple. Avec comme caractèristique
fondamentale le fait qu'il soit construit par l'objet auquel il
donne accès. Mais c'est vrai que le GoF donne surtout d'autres
exemples, dont certains que j'appellerais aussi automatiquement
un proxy -- surtout, par exemple, quand l'objet réel se trouve
sur un autre système. En revanche, je n'aurais pas pensé à leur
exemple de la création paresseuse comme un proxy. Mais pourquoi
pas.

En ce qui concerne façade, le GoF cite bien le cas d'une
interface unifiée à un ensemble de classes, comme utilisation
typique. L'aspect fondamental m'en semble « fournir une
interface simple à un sous-système complexe ». C'est vrai
que ce qui rend le sous-système complexe, souvent, c'est le
nombre de classes distinctes qui apparaissent au niveau de
l'interface. Mais dès que la classe en question présente une
abstraction plus simple, même si c'est d'une seule classe, je
crois qu'on pourrait parler d'une façade.

En fin de comple, est-ce que ça ne dépendrait pas plutôt de
pourquoi Fabien a créé cette classe intermédiaire ? Selon le
cas, adaptateur, passerelle et décorateur pourrait aussi
s'appliquait -- tous ces modèles ont comme caractèristique que
le client accède à une classe intermédiaire, et non à la classe
réele qui effectue la requête. (C'est aussi le cas de
lettre/envéloppe, avec la particularité dans lettre/envéloppe
que la classe effective dérive de la classe d'interface que voit
le client.)

J'avoue que j'ai une tendance à utiliser le mot « façade » comme
une désignation générique qui englobe tous ces modèles. Ce n'est
peut-être pas une bonne idée.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34




Avatar
Fabien LE LEZ
On Sun, 30 Apr 2006 12:09:39 +0200, James Kanze :

En fin de comple, est-ce que ça ne dépendrait pas plutôt de
pourquoi Fabien a créé cette classe intermédiaire ?


Si tu parles de <news:, la
réponse est dans le message : il s'agissait surtout de gérer la durée
de vie de l'objet initial, à la façon d'un pointeur intelligent, mais
sans le problème de "const" évoqué par l'OP -- un "HandleMachin const"
ne permet pas de modifier le "Machin" contenu.

1 2 3