OVH Cloud OVH Cloud

const & sur les POD

34 réponses
Avatar
David F.
Bonjour,
je lis parfois le code suivant (un poste récent le montre d'ailleurs):

1. int fct( const INT& n );

au lieu d'un simple

2. int fct ( INT n );

L'argument pour la forme 1. est (à mon sens):
- elle force la vérification que n ne sera pas
modifiée à l'intérieur de fct.
- elle force un passage sans recopie de n
( plus génant pour un vrai objet)
- elle évité d'avoir à réfléchir si INT est un POD ou un objet
complexe et hésiter entre la forme 1. et 2.

Honnêtement, même si j'utilise la forme 2. pour les POD, et la 1.
pour les objets. J'ai du mal à donner des arguments contre la forme 1
(au delà de la lourdeur de la notation).

Cordialement.

10 réponses

1 2 3 4
Avatar
Stan
----- Original Message -----
From: "David F."
Newsgroups: fr.comp.lang.c++
Sent: Saturday, October 15, 2005 1:04 PM
Subject: const & sur les POD


Bonjour,
je lis parfois le code suivant (un poste récent le montre d'ailleurs):

1. int fct( const INT& n );

au lieu d'un simple

2. int fct ( INT n );

L'argument pour la forme 1. est (à mon sens):
- elle force la vérification que n ne sera pas
modifiée à l'intérieur de fct.


Oui.

- elle force un passage sans recopie de n
( plus génant pour un vrai objet)


Plus génant ?
Au contraire passer un objet par référence offre un gain :
pas de construction ni de destruction de l'objet.

- elle évité d'avoir à réfléchir si INT est un POD ou un objet
complexe et hésiter entre la forme 1. et 2.



Il n'y a pas a hésiter puisque la première
forme est préférable ( sauf pour les types de base comme int ou double ).


Honnêtement, même si j'utilise la forme 2. pour les POD, et la 1.
pour les objets. J'ai du mal à donner des arguments contre la forme 1
(au delà de la lourdeur de la notation).


La "lourdeur de la notation" est largement compensée
par les avantages offerts.


--
-Stan

Avatar
Franck Branjonneau
"David F." écrivait:

1. int fct( const INT& n );
2. int fct ( INT n );

Honnêtement, même si j'utilise la forme 2. pour les POD, et la 1.
pour les objets.


Un POD est un objet -- enfin une instance d'un type POD est un objet...


Tu mélanges constance et passage par valeur ou par référence. Si n est
constant dans le corps de la fonction alors tu utilises 1 ou
3. int fct(INT const n);

Sinon tu utilises 2 ou
4. int fonc(INT& n);

Tu utilises le passage par référence si tu en as besoin pour le
typage dynamique ou si l'objet est gros (pour une définition ad hoc de
gros).
--
Franck Branjonneau

Avatar
Vincent Lascaux
Tu utilises le passage par référence si tu en as besoin pour le
typage dynamique ou si l'objet est gros (pour une définition ad hoc de
gros).


Est ce que le compilateur a le droit de modifier un passage par référence
const en un passage par valeur ?
En gros, si c'est plus rapide d'appeler

int foo(int i) { return i+1; }

que

int foo(const int& i) { return i+1; }

mais qu'on écrit quand même la deuxième forme (une bonne raison de faire ca
serait que foo est une fonction template, qu'on a écrit const T& parceque T
peut être très lourd à copier, et qu'on utilise foo<int>), est ce que le
compilo est en droit d'utiliser la premiere version ? Est ce qu'en pratique
les compilos le font ?

--
Vincent

Avatar
David F.
----- Original Message -----
From: "David F."

Bonjour,
je lis parfois le code suivant (un poste récent le montre d'ailleurs):

1. int fct( const INT& n );

au lieu d'un simple

2. int fct ( INT n );

L'argument pour la forme 1. est (à mon sens):
- elle force la vérification que n ne sera pas
modifiée à l'intérieur de fct.



Oui.


- elle force un passage sans recopie de n
( plus génant pour un vrai objet)



Plus génant ?
Au contraire passer un objet par référence offre un gain :
pas de construction ni de destruction de l'objet.



Quand je disais "( plus génant pour un vrai objet)"
je parlais de la recopie.



- elle évité d'avoir à réfléchir si INT est un POD ou un objet
complexe et hésiter entre la forme 1. et 2.




Il n'y a pas a hésiter puisque la première
forme est préférable ( sauf pour les types de base comme int ou double ).



Honnêtement, même si j'utilise la forme 2. pour les POD, et la 1.
pour les objets. J'ai du mal à donner des arguments contre la forme 1
(au delà de la lourdeur de la notation).



La "lourdeur de la notation" est largement compensée
par les avantages offerts.


--
-Stan





Avatar
David F.
"David F." écrivait:

1. int fct( const INT& n );
2. int fct ( INT n );

Honnêtement, même si j'utilise la forme 2. pour les POD, et la 1.
pour les objets.


Un POD est un objet -- enfin une instance d'un type POD est un objet...


Oui erreur de terme désolé, je voulais parler des types
fondamentaux ou primitifs ( int, float, double, ... ).

Tu mélanges constance et passage par valeur ou par référence. Si n est
constant dans le corps de la fonction alors tu utilises 1 ou
3. int fct(INT const n);


Je ne pense pas mélanger.
La forme 3 n'a pas l'avantage (événtuel) de ne passer qu'un
pointeur lors de l'appel de la fonction, ni d'éviter une recopie
pour les instances d'objets complexes.
(en plus d'être ignoré ou au pire signalé en warning car inutile)

Sinon tu utilises 2 ou
4. int fonc(INT& n);

Tu utilises le passage par référence si tu en as besoin pour le
typage dynamique ou si l'objet est gros (pour une définition ad hoc de
gros).


En fait, ça je le sais déjà.
Ce n'était pas tout à fait le sens de ma question.
Je n'arriverais décidément jamais à me faire comprendre
par écrit :(

Je retente ma chance avec une source externe.
Le sujet tourne autour du point 25 de "C++ Coding Standards"
Je traduis (enfin j'essaye) un cours etrait:

"Pour les paramètres en entrée seulement:
- toujours "const" qualifier les pointeurs ou références qui
ne seront pas modifiés
- préférer la copie pour les types primitives et ceux à faible
coût de copie ( Point, complex<float>, ... ) -> forme 2
- conséder le passage par valeur au lieu du passage par référence
si la fonction nécessite une copie de cet argument.
(équivalent à const + référence + création de la variable temporaire).
-> disons forme 2'

Pour les paramètres en sortie ou entrée/sortie:
- préférer les pointeurs pour les arguments optionnels, ou sauvegarde
une copie du pointeur, ou manipule le responsable du pointé.
- préférer une référence pour les arguments obligatoires (non soumis
aux points précédents)"

(Merci à Sutter et Alexandrescu)

Mon problème (et uniquement celui là) est que j'ai du mal
à justifié la forme 2 ou la forme 2' par rapport à la solution
tout "const &"
J'espère que c'est plus clair malgré ma traduction maladroite
(le mieux c'est d'avoir l'original sous les yeux, ou même la version
française qui est sortie ;) )


Avatar
James Kanze
Vincent Lascaux wrote:
Tu utilises le passage par référence si tu en as besoin pour
le typage dynamique ou si l'objet est gros (pour une
définition ad hoc de gros).



Est ce que le compilateur a le droit de modifier un passage
par référence const en un passage par valeur ?


Le compilateur a droit à faire tout ce qui ne modifie pas le
comportement visible du programme. S'il peut déterminer que le
résultat en est le même, il peut faire la modification.

En gros, si c'est plus rapide d'appeler


int foo(int i) { return i+1; }


que


int foo(const int& i) { return i+1; }


mais qu'on écrit quand même la deuxième forme (une bonne
raison de faire ca serait que foo est une fonction template,
qu'on a écrit const T& parceque T peut être très lourd à
copier, et qu'on utilise foo<int>), est ce que le compilo est
en droit d'utiliser la premiere version ?


Oui, à condition que ça ne modifie pas le comportement visible
(d'un programme légal).

À peu près la seule façon que ça pourrait modifier le
comportement visible, c'est si la fonction faisait un
const_cast, et modifiait en fait le paramètre. À partir de ce
moment, en revanche, l'appel de la fonction avec un objet const
ou avec un rvalue menerait à un comportement indéfini.

Est ce qu'en pratique les compilos le font ?


Sauf dans le cas des fonctions inline, je crois que cette
optimisation doit être extrèmement rare. Dans la pratique, les
compilateurs utilisent la même forme de l'appel partout. Il
faudrait donc où que le compilateur sache à chaque endroit d'un
appel qu'il n'y a pas de const_cast dans la fonction, soit que
le compilateur puisse voir un appel avec un rvalue ou un objet
const, et savoir qu'il puisse en voir un à chaque endroit de
l'appel.

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


Avatar
Franck Branjonneau
"David F." écrivait:

- préférer la copie pour les types primitives et ceux à faible
coût de copie ( Point, complex<float>, ... ) -> forme 2
- conséder le passage par valeur au lieu du passage par référence
si la fonction nécessite une copie de cet argument.
(équivalent à const + référence + création de la variable temporaire).
-> disons forme 2'

Mon problème (et uniquement celui là) est que j'ai du mal
à justifié la forme 2 ou la forme 2' par rapport à la solution
tout "const &"


Pour la forme 2 c'est parce que utiliser des valeurs laisse, à priori,
plus d'opportunités au compilateur de générer du code plus efficient.

Pour la forme 2' c'est une question de responsabilité. Si j'ai

struct S { S(const &) { throw "anything"; } };

int main() { S s; foo(s); }

Que se passe-t-il avec

void foo(S const & p) { S s(p); }

et avec

void foo(S p) { }

?
--
Franck Branjonneau

Avatar
Franck Branjonneau
"David F." écrivait:

Pour la forme 2' c'est une question de responsabilité. Si j'ai
struct S {


s() {}

S(const &) { throw "anything"; } };
int main() { S s; foo(s); }
Que se passe-t-il avec void foo(S const & p) { S s(p); }
et avec void foo(S p) { }
?


Je dirai que ça throw pour les deux.


Oui mais dans le premier cas c'est l'appelé qui lance l'exception alors
que dans le second cas c'est l'appelant.

un exemple d'utilisation de la forme 2' pourrait être
une fonction trim ne modifiant par l'argument.

par exemple :

std::string trim( const std::string& s )
{
try {


std::string tmp( s );


} catch(...) {

throw;
}
// trim tmp ...
return tmp;
}

la forme 2' donnerait :

std::string trim( std::string tmp )
{
// trim tmp ...
return tmp;
}

Ca ne m'aide pas vraiment pour justifier l'emploi de l'une
ou l'autre des "formes".

void SetSomeBoolFlag( bool theFlag );
ou
void SetSomeBoolFlag( const bool& theFlag );
// ou void SetSomeBoolFlag( bool const & theFlag );


Ici, c'est le cas 1. Tu choisis le passage par valeur.
--
Franck Branjonneau


Avatar
Franck Branjonneau
"David F." écrivait:


Ca ne m'aide pas vraiment pour justifier l'emploi de l'une
ou l'autre des "formes".

void SetSomeBoolFlag( bool theFlag );
ou
void SetSomeBoolFlag( const bool& theFlag );
// ou void SetSomeBoolFlag( bool const & theFlag );
Ici, c'est le cas 1. Tu choisis le passage par valeur.



Oui, mais pourquoi ?


Message-ID:
Pour la forme 2 c'est parce que utiliser des valeurs laisse, à priori,
plus d'opportunités au compilateur de générer du code plus efficient.
--
Franck Branjonneau



Avatar
Franck Branjonneau
"Vincent Lascaux" écrivait:

"Franck Branjonneau" a écrit dans le message de news:

"David F." écrivait:

Pour la forme 2' c'est une question de responsabilité. Si j'ai
struct S {
Je dirai que ça throw pour les deux.




Oui mais dans le premier cas c'est l'appelé qui lance l'exception alors
que dans le second cas c'est l'appelant.


Est ce qu'on peut être sur de ca ?


Oui.

Le compilo n'est il pas en droit d'inliner ?


Message-ID: <4351803f$0$21699$
Le compilateur a droit à faire tout ce qui ne modifie pas le
comportement visible du programme. S'il peut déterminer que le
résultat en est le même, il peut faire la modification.

Essaie

void foo(S const p) { std::cout << "In foon"; }
void foo(S const & p) { std::cout << "In foon"; S s(p); }

pour t'en persuader.
--
Franck Branjonneau





1 2 3 4