OVH Cloud OVH Cloud

Liste variable d'arguments

20 réponses
Avatar
Etienne Rousee
Bonjour,

Y a-t-il quelque chose de non standard dans ceci:

J'ai une classe Point2D contenant deux doubles
x et y et un constructeur, puis une classe Poly2D
contenant un deque<Point2D>. Le problème se
pose avec le constructeur de Poly2D qui admet
une liste variable d'arguments.
Voici son code:

// Début code -------------------

Poly2D::Poly2D(Point2D premier, ... )
{
va_list vl;
Point2D suivant;

if (premier == FIN) return;

va_start(vl,premier);
_pol.push_back(premier);
while (suivant != FIN)
{
suivant = va_arg(vl,Point2D);
if (suivant != FIN) _pol.push_back(suivant);
}
va_end(vl);
}

// Fin code -------------------

FIN est un Point2D fixe servant de marqueur de fin.

Puis je teste en mettant 3 Point2D en dur et en
écrivant Poly2D Pol(A,B,C,FIN);

Sous VC++6.0 tout fonctionne, 0 errors et 0 warnings,
et l'exécution donne ce que je veux.
Sous CodeBlocks, 0 errors et 1 warning que je ne comprends
pas, et la ligne de test ci dessus fait sortir du programme,
mais sans la fenêtre d'erreur windows traditionnelle.

Le warning est:
"cannot receive objects of non-POD type"
puis:
"cannot pass objects of non-POD type" 3 fois.

Que signifie POD ?
Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?

--

Etienne

10 réponses

1 2
Avatar
Sylvain
Etienne Rousee wrote on 21/02/2007 13:31:

Sous VC++6.0 tout fonctionne, 0 errors et 0 warnings,
et l'exécution donne ce que je veux.
Sous CodeBlocks, 0 errors et 1 warning que je ne comprends
pas, et la ligne de test ci dessus fait sortir du programme,
mais sans la fenêtre d'erreur windows traditionnelle.


le fil a débattu de ce point, il y a peu, me référrant au comportement
de VC, je partageais cette surprise d'y voir qlq chose de non standard.
le fait est qu'en effet seul des POD peuvent être gérés proprement par
les fonctions / macros (implémentation dépendent) va_xxx.

Que signifie POD ?


plain old data.
un type primitif, un pointeur, des struct statiques (sans opérateur &) ...

Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?


un moyen est de transmettre une liste variable de pointeurs.

une autre façon possible est, au niveau de l'appelant, d'initialiser et
remplir un autre container et de transmettre ce seul container (à la
place des items), la méthode transférera alors les éléments de la "liste
de transport" à la liste de l'instance.

Sylvain.

Avatar
Mathias Gaunard

Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?


L'overloading.

Avatar
Etienne Rousee
"Mathias Gaunard" a écrit ...

Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?


L'overloading.


Q'est ce que c'est ?

--

Etienne


Avatar
Etienne Rousee
"Sylvain" a écrit ...
Etienne Rousee wrote on 21/02/2007 13:31:
Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?


un moyen est de transmettre une liste variable de pointeurs.


Merci beaucoup pour ces renseignements.
Je vais essayer avec des pointeurs.

une autre façon possible est, au niveau de l'appelant, d'initialiser et
remplir un autre container et de transmettre ce seul container (à la
place des items), la méthode transférera alors les éléments de la "liste
de transport" à la liste de l'instance.


Je vais y réfléchir.

--

Etienne


Avatar
Mathias Gaunard

Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?
L'overloading.



Q'est ce que c'est ?


void foo();
void foo(T1 t1);
void foo(T1 t1, T2 t2);
void foo(T1 t1, T2 t2, T3 t3);
void foo(T1 t1, T2 t2, T3 t3, T4 t4);

etc. jusqu'à une profondeur arbitraire.

Tu peux aussi utiliser les variadic templates pour générer les fonctions
dont tu as besoin au fur et à mesure que tu les utilises.



Avatar
Sylvain
Etienne Rousee wrote on 21/02/2007 19:22:
"Mathias Gaunard" a écrit ...

Y a-t-il une autre façon d'obtenir une liste variable d'arguments ?
L'overloading.



Q'est ce que c'est ?


sur *fr*.comp... on appelle cela la surcharge (de méthodes).

définir autant de convention d'appels que nécessaire est, en effet,
possible puisque l'info nécessaire est dispo durant la compil. ... mais
quelle lourdeur.

Sylvain.



Avatar
James Kanze
Etienne Rousee wrote:

Y a-t-il quelque chose de non standard dans ceci:

J'ai une classe Point2D contenant deux doubles
x et y et un constructeur, puis une classe Poly2D
contenant un deque<Point2D>. Le problème se
pose avec le constructeur de Poly2D qui admet
une liste variable d'arguments.
Voici son code:

// Début code -------------------

Poly2D::Poly2D(Point2D premier, ... )
{
va_list vl;
Point2D suivant;

if (premier == FIN) return;

va_start(vl,premier);
_pol.push_back(premier);
while (suivant != FIN)
{
suivant = va_arg(vl,Point2D);
if (suivant != FIN) _pol.push_back(suivant);
}
va_end(vl);
}

// Fin code -------------------

FIN est un Point2D fixe servant de marqueur de fin.


Comme a dit Sylvain, c'est un comportement indéfini. (En fait,
avec un compilateur moderne, on pourrait toujours le faire
marcher, mais historiquement, assurer la destruction sans savoir
combien d'éléments il y avait posait pas mal de problèmes.)

La solution de Sylvain, avec des pointeurs (et un pointeur nul
comme marque de fin) marche, mais a deux inconveniants :
il ne permet pas de passer des temporaires (des expressions du
genre « Point2D( 1.2, 3.4 ) »), et il est trop facile
d'oublier le pointeur nul de fin (pointeur qui, au moins
formellement, doit avoir le type Point2D const* -- un 0 ou le
symbole NULL tout court ne suffit pas). Alors, il y a deux
solutions classiques :

celle qui est à la mode :

On fait du constructeur un template, et on lui passe des
itérateurs :

template< typename FwdIter >
Poly2D::Poly2D( FwdIter begin, FwdIter end )
: _pol( begin, end )
{
}

L'avantage, évidemment, c'est que tu te passes de toute la
logique de la boucle dans ton code. En plus, si les
itérateurs sont à accès aléatoire, l'implémentation de
vector en profitera pour calcule la taille au départ, et ne
faire qu'une seule allocation.

Le désavantage, évidemment, c'est que ça ne fait pas
réelement la même chose. Il faut, par exemple, que tu as des
paramètres quelque part d'où tu peux tirer des itérateurs,
ce qui est loin d'être évident.

celle qui marche réelement :

On crée une classe « collecteur », qu'on passe. Le
constructeur devient à peu près :

Poly2D::Poly2D( Collector< Point2D > const& collector )
: _pol( collector.begin(), collector.end() )
{
}

Le collecteur, au font, ce n'est rien d'autre qu'une classe
qui contient un std::vector, et qui fournit aussi un moyen
d'enchaîner des push_back ; std::vector lui-même ferait
l'affaire si push_back renvoiait une référence à la
collection. (Mais le nom push_back est un peu long pour
cette utilisation. On préfère « with », voire l'opérateur
<< ou l'opérateur().) Quelque chose du genre ::

template< typename T >
class Collector
{
public:
Collector& with( T const& elem )
// ou Collector& operator()( T const& elem )
{
myData.push_back( elem ) ;
return *this ;
}
std::vector< T >::const_iterator begin() const
{
return myData.begin() ;
}
std::vector< T >::const_iterator end() const
{
return myData.end() ;
}

private:
std::vector< T > myData ;
} ;

Et ensuite :

typedef Collector< Point2D >
Poly2DCollect ;
Poly2D poly( Poly2DCollect().with( Point2D( 1.2, 3.4 ) )
.with( unAutrePoint )
.with( encoreUn ) ) ;

(Si on surcharge l'opérateur () à la place, ôte les
« .with ». Je le trouve légèrement plus lisible avec les
« .with », mais AMHA la différence n'est pas énorme.)

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :

Collector< Point2D >
operator,( Point2D const& first, Point2D const& second )
{
return Collector< Point2D
().with( first ).with( second ) ;
}


et comme membre (afin de pouvoir l'appeler sur un
temporaire) :

template< typename T >
Collector< T >& Collector< T >::operator,( T const& elem )
{
myData.push_back( elem ) ;
return *this ;
}

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;

(On remarque le deuxième jeu de parenthèse. C'est pourquoi
je le considère un peu l'obfuscation ; quelqu'un qui lit le
code risque de ne pas les voir de premier abord, et de ne
pas comprendre ce qui se passe. Mais au moins, si tu les
oublies, le compilateur râle.)

Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée. (Je me vois mal taper une centaine
de constructeurs, tous rigueureusement identique sauf en ce qui
concerne le nombre de paramètres. Mais un petit script d'AWK...)

--
James Kanze (GABI Software) email:
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
Etienne Rousee
"James Kanze" a écrit ...
Comme a dit Sylvain, c'est un comportement indéfini. (En fait,
avec un compilateur moderne, on pourrait toujours le faire
marcher, mais historiquement, assurer la destruction sans savoir
combien d'éléments il y avait posait pas mal de problèmes.)


Il suffit que Poly2D ait un destructeur correct, non ?

La solution de Sylvain, avec des pointeurs (et un pointeur nul
comme marque de fin) marche, mais a deux inconveniants :
il ne permet pas de passer des temporaires (des expressions du
genre « Point2D( 1.2, 3.4 ) »),


Ben, ça me gêne aussi.

et il est trop facile d'oublier le pointeur nul de fin


C'est de la responsabilité du programmeur.

(pointeur qui, au moins formellement, doit avoir
le type Point2D const* -- un 0 ou le symbole NULL
tout court ne suffit pas).


Un cast dans le constructeur ne suffit pas ?
Par exemple:
if (premier == (Point2D *) NULL) ...........

Alors, il y a deux solutions classiques :

celle qui est à la mode :
...........
Il faut, par exemple, que tu as des paramètres
quelque part d'où tu peux tirer des itérateurs,
ce qui est loin d'être évident.


Je n'en ai pas justement.

celle qui marche réelement :

On crée une classe « collecteur », qu'on passe. Le
constructeur devient à peu près :

Poly2D::Poly2D( Collector< Point2D > const& collector )
: _pol( collector.begin(), collector.end() )
{
}


Il y a une raison particulière pour que le collecteur soit template ?

Le collecteur, au font, ce n'est rien d'autre qu'une classe
qui contient un std::vector, et qui fournit aussi un moyen
d'enchaîner des push_back ; std::vector lui-même ferait
l'affaire si push_back renvoiait une référence à la
collection.


Est ce que Poly2D elle-même pourrait faire l'affaire ?

[snip code]


Ce code me paraît clair.

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :


J'aime bien :)

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;


Ce qui est exactement ce que je veux pouvoir écrire.
Tant pis pour les doubles parenthèses, il suffit de le savoir
et de le commenter.

Enfin, si tu as une borne supérieur au nombre d'éléments, il y a
toujours le surcharge. Un peu pénible à entrée (surtout si la
borne supérieur est élevée.


Non, ça peut être quelconque.

Merci en tout cas pour toutes ces précisions.

--

Etienne

Avatar
Sylvain
Etienne Rousee wrote on 22/02/2007 17:46:
"James Kanze" a écrit ...
Comme a dit Sylvain, c'est un comportement indéfini. (En fait,
avec un compilateur moderne, on pourrait toujours le faire
marcher, mais historiquement, assurer la destruction sans savoir
combien d'éléments il y avait posait pas mal de problèmes.)


Il suffit que Poly2D ait un destructeur correct, non ?


non, le pb est sur le destructeur des paramètres temporaires (Point2D)
pas sur la classe proposant la méthode recevant ses params.

La solution de Sylvain, avec des pointeurs (et un pointeur nul
comme marque de fin) marche, mais a deux inconveniants :
il ne permet pas de passer des temporaires (des expressions du
genre « Point2D( 1.2, 3.4 ) »),


Ben, ça me gêne aussi.


si rien d'autre n'est basé sur des pointeurs de point, ce serait
nuisible (en plus de génant).

(pointeur qui, au moins formellement, doit avoir
le type Point2D const* -- un 0 ou le symbole NULL
tout court ne suffit pas).


Un cast dans le constructeur ne suffit pas ?
Par exemple:
if (premier == (Point2D *) NULL) ...........


le pb est de cast fait par va_arg.

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :


J'aime bien :)


et comment ! ;)
dommage même que tu utilises des instances non mutable, un:

Poly2D poly;
poly = Point2D(a,b), Point2D(c,d), Point2D(e,f);

aurait également été sympathiquement obfusquant.

Merci en tout cas pour toutes ces précisions.


je m'associe - mes règles ne sont pas aussi élégantes que celles de
James et l'exemple proposé est très bien posé.

Sylvain.


Avatar
Mathias Gaunard
"James Kanze" a écrit ...

Si on aime l'obfuscation, on peut même ajouter des
surcharges de l'opérateur virgule :


J'aime bien :)

Ce qui permet l'écriture :

Poly2D poly( (Point2D( 1.2, 3.4 ),
unAutrePoint,
etEncoreUn) ) ;


Ce qui est exactement ce que je veux pouvoir écrire.
Tant pis pour les doubles parenthèses, il suffit de le savoir
et de le commenter.



Tu peux aussi utiliser d'autres opérateurs qui ne nécessiteront alors
pas de parenthèses.

Poly2D poly( Point2D(1.2, 3.4) % unAutrePoint % etEncoreUn );


1 2