OVH Cloud OVH Cloud

types d'objets dans une std::map et templates

12 réponses
Avatar
Greg
Bonsoir,

sous ce titre confus se cache un probl=E8me qui l'est tout autant pour
moi, et ma faible exp=E9rience du c++ (je ne pratique pas assez) fait que
je n'arrive pas =E0 trouver le vocabulaire ad=E9quat.

Mon probl=E8me est le suivant : je souhaite r=E9aliser un ensemble de
classes qui vont me permettre de lire un fichier de configuration (texte
simple, avec une syntaxe classique variable=3Dvaleur) et de typer les
variables rencontr=E9es dans le fichier en fonction de certains
param=E8tres. Ces param=E8tres se trouveraient dans un second fichier dont
les lignes seraient de la forme variable=3Dtype (avec type choisi dans une
liste fixe).

Dans le programme, le but est d'obtenir une syntaxe la plus simple
possible de style :


configuration* c =3D new configuration(fichier_config, fichier_params);

// exemple pour un entier quelconque, "nom_de_l_entier" est
// le nom de la variable dans le fichier de config
int entier_quelconque =3D c["nom_de_l_entier"];


Fort na=EFvement, et n'ayant pas bien compris les templates j'avais
commenc=E9 =E0 cr=E9er une version de d=E9mo en d=E9finissant une
std::map<std::string, config_value<T>* > et une classe config_value
d=E9finie comme suit :

template <class T> class config_value
{
T value;
public:
config_value(T& t) : value(t) {}
}

Maintenant que j'ai bien relu le chapitre sur les templates, je me rend
compte de l'absurdit=E9 de ce syst=E8me, ne tapez pas. Cependant je tiens
toujours =E0 la simplicit=E9 d'utilisation d=E9crite plus haut, mais je
n'arrive pas =E0 trouver de syst=E8me qui me permette de stocker dans une
std::map des types h=E9t=E9rog=E8nes, ou qui au moins sont per=E7us comme
h=E9t=E9rog=E8nes par les utilisateurs de la classe.

Bref, existe-t-il une solution =E0 ce probl=E8me ou dois-je me rabattre sur
une solution de repli moins pratique ?

Merci,
Greg

10 réponses

1 2
Avatar
Christophe Lephay
"Greg" a écrit dans le message de news:

std::map<std::string, config_value<T>* > et une classe config_value
définie comme suit :

template <class T> class config_value
{
T value;
public:
config_value(T& t) : value(t) {}
}

Maintenant que j'ai bien relu le chapitre sur les templates, je me rend
compte de l'absurdité de ce système, ne tapez pas. Cependant je tiens
toujours à la simplicité d'utilisation décrite plus haut, mais je
n'arrive pas à trouver de système qui me permette de stocker dans une
std::map des types hétérogènes, ou qui au moins sont perçus comme
hétérogènes par les utilisateurs de la classe.

Bref, existe-t-il une solution à ce problème ou dois-je me rabattre sur
une solution de repli moins pratique ?


Il faut que tous tes config_value< T > dérivent d'une classe commune et
faire de cette classe le value_type de ton map (enfin des pointeurs, comme
tu le fais déjà dans ton code).

Chris

Avatar
Twxs
Greg wrote:
Bonsoir,


Bref, existe-t-il une solution à ce problème ou dois-je me rabattre sur
une solution de repli moins pratique ?

Merci,
Greg


regarder du cotre de boost::any ou boost::varient, ca devrait pouvoir
faire ca

Twxs

Avatar
drkm
Greg writes:

Bref, existe-t-il une solution à ce problème ou dois-je me rabattre sur
une solution de repli moins pratique ?


Que pensez-vous de ceci ?

~> cat fclcxx.cc
#include <iostream>
#include <ostream>
#include <map>
#include <string>

struct Value {
} ;

template< typename T >
struct TypedValue : Value {
TypedValue( T const & value )
: myValue( value ) {
}
T myValue ;
} ;

class Variables {
typedef std::map< std::string , Value * >
Map ;
public:
~Variables() {
for( Map::iterator it = myVariables.begin() ,
end = myVariables.end() ;
it != end ;
++ it ) {
delete it->second ;
}
}
template< typename T >
void put(
std::string const & name ,
T const & value
) {
Map::iterator it = myVariables.find( name ) ;
if ( it != myVariables.end() ) {
delete it->second ;
}
myVariables[ name ] = new TypedValue< T >( value ) ;
}
template< typename T >
void get(
std::string const & name ,
T * & value
) {
typedef TypedValue< T > * TVPtr ;
Map::iterator it = myVariables.find( name ) ;
if ( it == myVariables.end() ) {
value = 0 ;
}
else {
TVPtr v = static_cast< TVPtr >( it->second ) ;
value = & v->myValue ;
}
}
template< typename T >
void get(
std::string const & name ,
T const * & value
) const {
typedef TypedValue< T > const * TVPtr ;
Map::const_iterator it = myVariables.find( name ) ;
if ( it == myVariables.end() ) {
value = 0 ;
}
else {
TVPtr v = static_cast< TVPtr >( it->second ) ;
value = & v->myValue ;
}
}
private:
Map myVariables ;
} ;

void display( Variables const & vars ) {
int const * i = 0 ;
std::string const * s = 0 ;

vars.get( "integer" , i ) ;
vars.get( "string" , s ) ;

if ( i ) {
std::cout << "integer=" << * i << std::endl ;
}
else {
std::cout << "integer=<not set>" << std::endl ;
}
if ( s ) {
std::cout << "string=" << * s << std::endl ;
}
else {
std::cout << "string=<not set>" << std::endl ;
}
}

int main() {
Variables vars ;

display( vars ) ;

vars.put( "integer" , 1024 ) ;
vars.put( "string" , std::string( "yo" ) ) ;

display( vars ) ;

vars.put( "string" , std::string( "re-yo" ) ) ;

display( vars ) ;
}
~> g++ -o fclcxx fclcxx.cc -Wall -W -ansi -pedantic
~> ./fclcxx
integer=<not set>
string=<not set>
integer24
string=yo
integer24
string=re-yo

Ce n'est qu'un point de départ, il y a plusieurs variantes
possibles. Je dirais que c'est un exemple ou les Policy Classes
devraient être utile.

--drkm

Avatar
kanze
Greg wrote:

sous ce titre confus se cache un problème qui l'est tout autant pour
moi, et ma faible expérience du c++ (je ne pratique pas assez) fait
que je n'arrive pas à trouver le vocabulaire adéquat.

Mon problème est le suivant : je souhaite réaliser un ensemble de
classes qui vont me permettre de lire un fichier de configuration
(texte simple, avec une syntaxe classique variable=valeur) et de
typer

les variables rencontrées dans le fichier en fonction de certains
paramètres. Ces paramètres se trouveraient dans un second fichier
dont

les lignes seraient de la forme variable=type (avec type choisi dans
une liste fixe).

Dans le programme, le but est d'obtenir une syntaxe la plus simple
possible de style :

configuration* c = new configuration(fichier_config, fichier_params);

// exemple pour un entier quelconque, "nom_de_l_entier" est
// le nom de la variable dans le fichier de config
int entier_quelconque = c["nom_de_l_entier"];

Fort naïvement, et n'ayant pas bien compris les templates j'avais
commencé à créer une version de démo en définissant une
std::map<std::string, config_value<T>* > et une classe config_value
définie comme suit :

template <class T> class config_value
{
T value;
public:
config_value(T& t) : value(t) {}
}

Maintenant que j'ai bien relu le chapitre sur les templates, je me
rend compte de l'absurdité de ce système, ne tapez pas. Cependant
je

tiens toujours à la simplicité d'utilisation décrite plus haut,
mais

je n'arrive pas à trouver de système qui me permette de stocker
dans

une std::map des types hétérogènes, ou qui au moins sont perçus
comme

hétérogènes par les utilisateurs de la classe.

Bref, existe-t-il une solution à ce problème ou dois-je me rabattre
sur une solution de repli moins pratique ?


Comme d'autres ont déjà signalé, il existe boost::any, ou sinon tu
peux
créer ton propre hiérarchie. Mais je me pose la question : tu lis les
valeurs en tant que texte, pourquoi pas les stocker aussi sous ce
format, et ne convertir qu'en cas de besoin ? C-à-d grosso modo que ta
classe Configuration contiendrait un std::map<std::string,std::string>
tout bêtement, et que soit tu effectues les conversions dans des
fonctions du genre getInt, getString, etc., soit ta fonction get
renvoie
un proxy avec un opérateur de conversion templatée.

Et quant à ton fichier de paramètres, lors de la lecture, on se
contenterait de valider le format de la chaîne donnée comme valeur
lors
de la lecture.

En passant, j'étais en train de penser une chose à peu près
semblable
moi-même, au début à cause de la difficulté de propager des erreurs
dans
le cas d'utilisation d'une proxy. J'ai fini par conclure qu'il faut
savoir non seulement le type, mais si le paramètre est obligatoire ou
non, ou sinon une valeur par défaut (qui manque dans le cas d'un
paramètre par défaut). La solution que j'ai adopté n'est pas une
fichier
de paramètre (mais l'idée n'est pas mal), mais une solution à peu
près
comme celle qui me sert actuellement pour les options de la ligne de
commande (voir le composant CommandLine sur ma site). Avec le fichier
de
paramètres, en revanche, ça pourrait donner quelque chose du genre :

<nom> = <type> [ ':' <valeur> ]

Sans le ':', le paramètre est obligatoire dans le fichier de config.
Avec le ':', le paramètre a <valeur> (qui peut être vide) comme
valeur
par défaut.

--
James Kanze GABI Software http://www.gabi-soft.fr
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
Greg
On Tue, 07 Dec 2004 01:42:47 +0100
drkm wrote:

Bonjour,

sans accès au net pendant 2 jours, j'ai eu le temps de bosser sur une
version de la classe de base qui va servir à stocker les valeurs de
configuration (le code est à la fin de ce mail), mais j'ai pas mal de
question (de débutant je suppose) sur votre bout de code.

struct Value {
} ;


Il ne faut pas définir ici un destructeur virtuel vu que cette classe
est appelée à n'être qu'une classe de base ?

class Variables {
typedef std::map< std::string , Value * >
Map ;
public:
~Variables() {
for( Map::iterator it = myVariables.begin() ,
end = myVariables.end() ;
it != end ;
++ it ) {
delete it->second ;
}
}


Pourquoi appeler les destructeurs de chaque élément de la std::map ? Son
destructeur ne le fait pas ?

[snip de la fin de la classe Variables]


void display( Variables const & vars ) {
int const * i = 0 ;
std::string const * s = 0 ;

vars.get( "integer" , i ) ;
vars.get( "string" , s ) ;

if ( i ) {
std::cout << "integer=" << * i << std::endl ;
}
else {
std::cout << "integer=<not set>" << std::endl ;
}
if ( s ) {
std::cout << "string=" << * s << std::endl ;
}
else {
std::cout << "string=<not set>" << std::endl ;
}
}


Le souci c'est qu'avec cette fonction il faut prévoir un cas par type
intégré ou des conversions implicites sont-elles réalisées ?

Ce n'est qu'un point de départ, il y a plusieurs variantes
possibles. Je dirais que c'est un exemple ou les Policy Classes
devraient être utile.


Je vais googler un coup sur "policy classes", merci pour le code. Voilà
ce que j'ai laborieusement pondu :

--8<-------------------------------------------------------------------

#include <iostream>
#include <map>


class untyped_value
{
public:
virtual ~untyped_value() {}
};


template<class T>
class typed_value : public untyped_value
{
public:
typed_value(const T& t) : value(t) {}
T& get_val() { return value; }
private:
T value;
};


class config_value
{
public:
config_value() : conf(0) {}
~config_value() { delete conf; }

template<class T> config_value(const T& t)
{
conf = new typed_value<T>(t);
}

template<class T> void operator=(const T& t)
{
if(conf) delete conf;
conf = new typed_value<T>(t);
}

template<class T> operator T()
{
return dynamic_cast<typed_value<T>* >(conf)->get_val();
}

private:
untyped_value* conf;
};

--8<-------------------------------------------------------------------


En lisant les autres mails qui m'ont dirigé vers boost::any, je me rends
compte que j'ai presque réussi à réinventer la roue, mais avec des
soucis pour la surcharge de "operator<<" (fonction non présente dans ce
code). Si je la déclare comme suit en tant que membre de la classe
config_value :

template<class T> std::ostream& operator(std::ostream& s);

la fonction ne semble pas prise en compte lors de la résolution de
surcharge. Y a-t-il une raison à ça ?

Merci,
Greg

Avatar
Greg
On 7 Dec 2004 00:27:27 -0800
wrote:

Mais je me pose la question : tu lis les
valeurs en tant que texte, pourquoi pas les stocker aussi sous ce
format, et ne convertir qu'en cas de besoin ? C-à-d grosso modo que ta
classe Configuration contiendrait un std::map<std::string,std::string>
tout bêtement, et que soit tu effectues les conversions dans des
fonctions du genre getInt, getString, etc., soit ta fonction get
renvoie un proxy avec un opérateur de conversion templatée.


J'avais utilisé les stringstreams pour faire ce genre de choses dans un
petit bout de programme, est-ce que vous parlez bien de ça ? Auquel cas,
je ne vois pas trop l'intérêt d'un proxy (ou alors je n'ai pas compris
ce qu'est un proxy).

J'ai fini par conclure qu'il faut
savoir non seulement le type, mais si le paramètre est obligatoire ou
non, ou sinon une valeur par défaut (qui manque dans le cas d'un
paramètre par défaut).


J'avais pensé coder les valeurs en dur dans l'appli, via une interface
de la classe configuration, mais la syntaxe qui suit est intéressante.

<nom> = <type> [ ':' <valeur> ]

Sans le ':', le paramètre est obligatoire dans le fichier de config.
Avec le ':', le paramètre a <valeur> (qui peut être vide) comme
valeur par défaut.


J'avais déjà envisagé une syntaxe plus complexe à analyser, mais pl us
pratique à mon sens pour l'écriture des fichiers de config. Avec vos
ajouts ça donnerait :

group [type] {
variable1:valeur1
variable2:valeur2
variable3:
}

où [type] est choisi dans les types intégrés de C++. Ça demande plu s de
boulot pour l'analyse du fichier, mais ça permet de jouer au fainéant
sur le long terme.

a+,
Greg

Avatar
kanze
Greg wrote:
On 7 Dec 2004 00:27:27 -0800
wrote:

Mais je me pose la question : tu lis les
valeurs en tant que texte, pourquoi pas les stocker aussi sous ce
format, et ne convertir qu'en cas de besoin ? C-à-d grosso modo
que ta


classe Configuration contiendrait un
std::map<std::string,std::string>


tout bêtement, et que soit tu effectues les conversions dans des
fonctions du genre getInt, getString, etc., soit ta fonction get
renvoie un proxy avec un opérateur de conversion templatée.


J'avais utilisé les stringstreams pour faire ce genre de choses dans
un petit bout de programme, est-ce que vous parlez bien de ça ?
Auquel

cas, je ne vois pas trop l'intérêt d'un proxy (ou alors je n'ai pas
compris ce qu'est un proxy).


L'intérêt du proxy, c'est seulement que l'utilisateur n'a qu'une
seule
fonction à appeler :

std::string opt1 = Configuration::instance().get( "opt1" ) ;
int opt2 = Configuration::instance().get( "opt2" ) ;
// etc.

Alternativement, il faut que l'utilisateur spécifie le type dans le
nom
de la fonction, par exemple :

std::string opt1 = Configuration::instance().get< std::string >(
"opt1" ) ;
int opt2 = Configuration::instance().get< int >( "opt2" ) ;
// etc.

Mais plus j'y pense, plus je crois que j'utiliserais un méchanisme un
peu comme celle que j'utilise déjà pour les options de la ligne de
commande. C-à-d que l'utilisateur déclarerait simplement des
variables à
une portée de namespace :

ConfigParam< std::string > opt1( "opt1", "quelque chose par
défaut" ) ;
ConfigParam< int > opt1( "opt2" ) ;
// L'absence d'un défaut signal que c'est une
// erreur si le fichier de configuration ne
// contient pas l'entrée.

Par la suite, on accède aux variables, comme si elles avaient en fait
le
type std::string const ou int const.

J'ai fini par conclure qu'il faut savoir non seulement le type,
mais


si le paramètre est obligatoire ou non, ou sinon une valeur par
défaut (qui manque dans le cas d'un paramètre par défaut).


J'avais pensé coder les valeurs en dur dans l'appli, via une
interface

de la classe configuration, mais la syntaxe qui suit est
intéressante.


<nom> = <type> [ ':' <valeur> ]

Sans le ':', le paramètre est obligatoire dans le fichier de
config.


Avec le ':', le paramètre a <valeur> (qui peut être vide) comme
valeur par défaut.


J'avais déjà envisagé une syntaxe plus complexe à analyser, mais
plus

pratique à mon sens pour l'écriture des fichiers de config. Avec
vos

ajouts ça donnerait :

group [type] {
variable1:valeur1
variable2:valeur2
variable3:
}

où [type] est choisi dans les types intégrés de C++. Ça demande
plus

de boulot pour l'analyse du fichier, mais ça permet de jouer au
fainéant sur le long terme.


Le problème que je vois, dans tous ces solutions, c'est qu'il faut un
répertoire (fichier ou autre) unique pour tous les paramètres. Ce qui
suppose qu'il y a une poste centrale qui les connaît. Ce qui n'est pas
le cas avec la solution que je propose avec les variables. En
général,
ce n'est pas un problème, parce qu'il faut la poste centrale de toute
façon -- il faut bien documenter tous les paramètres, par exemple.
Mais
on peut imaginer les cas où la documentation pour le programme X fait
référence à la documentation d'un sous-système dont il se sert --
c'est
courant, par exemple, que des applications pour X Windows contient
simplement une ligne qui dit quelque chose du genre « En plus, toutes
les options standard des applications X Windows sont supportées. »

--
James Kanze GABI Software http://www.gabi-soft.fr
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
drkm
writes:

Alternativement, il faut que l'utilisateur spécifie le type dans le
nom
de la fonction, par exemple :

std::string opt1 = Configuration::instance().get< std::string >(
"opt1" ) ;
int opt2 = Configuration::instance().get< int >( "opt2" ) ;
// etc.


Je trouve intéressant d'utiliser ici un argument destiné à recevoir
la valeur, pour résoudre l'appel :

Configuration::instance.get( "opt1" , une_chaine ) ;
Configuration::instance.get( "opt2" , un_entier ) ;
// etc.

En plus de la version à un argument.

--drkm

Avatar
drkm
Greg writes:

On Tue, 07 Dec 2004 01:42:47 +0100
drkm wrote:

struct Value {
} ;


Il ne faut pas définir ici un destructeur virtuel vu que cette classe
est appelée à n'être qu'une classe de base ?


Si. En même temps, aucune classe ne déclare de destructeur.

class Variables {
typedef std::map< std::string , Value * >
Map ;
public:
~Variables() {
for( Map::iterator it = myVariables.begin() ,
end = myVariables.end() ;
it != end ;
++ it ) {
delete it->second ;
}
}


Pourquoi appeler les destructeurs de chaque élément de la std::map ? Son
destructeur ne le fait pas ?


Si. Mais souvent, le destructeur d'un pointeur n'est pas suffisant.

[snip de la fin de la classe Variables]

void display( Variables const & vars ) {
int const * i = 0 ;
std::string const * s = 0 ;

vars.get( "integer" , i ) ;
vars.get( "string" , s ) ;

if ( i ) {
std::cout << "integer=" << * i << std::endl ;
}
else {
std::cout << "integer=<not set>" << std::endl ;
}
if ( s ) {
std::cout << "string=" << * s << std::endl ;
}
else {
std::cout << "string=<not set>" << std::endl ;
}
}


Le souci c'est qu'avec cette fonction il faut prévoir un cas par type
intégré ou des conversions implicites sont-elles réalisées ?


Je ne comprends pas la question.

Ce n'est qu'un point de départ, il y a plusieurs variantes
possibles. Je dirais que c'est un exemple ou les Policy Classes
devraient être utile.


Je vais googler un coup sur "policy classes", merci pour le code. Voilà
ce que j'ai laborieusement pondu :

--8<-------------------------------------------------------------------

#include <iostream>
#include <map>

class untyped_value
{
public:
virtual ~untyped_value() {}
};

template<class T>
class typed_value : public untyped_value
{
public:
typed_value(const T& t) : value(t) {}
T& get_val() { return value; }
private:
T value;
};

class config_value
{
public:
config_value() : conf(0) {}
~config_value() { delete conf; }

template<class T> config_value(const T& t)
{
conf = new typed_value<T>(t);
}

template<class T> void operator=(const T& t)
{
if(conf) delete conf;
conf = new typed_value<T>(t);
}

template<class T> operator T()


Je me méfie des conversions implicites.

{
return dynamic_cast<typed_value<T>* >(conf)->get_val();
}

private:
untyped_value* conf;
};

--8<-------------------------------------------------------------------

En lisant les autres mails qui m'ont dirigé vers boost::any, je me rends
compte que j'ai presque réussi à réinventer la roue, mais avec des
soucis pour la surcharge de "operator<<" (fonction non présente dans ce
code). Si je la déclare comme suit en tant que membre de la classe
config_value :

template<class T> std::ostream& operator(std::ostream& s);

la fonction ne semble pas prise en compte lors de la résolution de
surcharge. Y a-t-il une raison à ça ?


As-tu essayé :

TaClasse t ;
t << std::cout ;

?

Au fait, à quoi sert l'utilisation d'un modèle, dans ce cas ?

--drkm


Avatar
kanze
Greg wrote:
On Tue, 07 Dec 2004 01:42:47 +0100
drkm wrote:

sans accès au net pendant 2 jours, j'ai eu le temps de bosser sur
une

version de la classe de base qui va servir à stocker les valeurs de
configuration (le code est à la fin de ce mail), mais j'ai pas mal
de

question (de débutant je suppose) sur votre bout de code.

struct Value {
} ;


Il ne faut pas définir ici un destructeur virtuel vu que cette
classe

est appelée à n'être qu'une classe de base ?


Absolument. C'est une bonne précaution la plupart du temps, et ici, on
appelle bien delete sur un Value* plus bas ; sans le destructeur
virtuel, le code a un comportement indéfini.

class Variables {
typedef std::map< std::string , Value * >
Map ;
public:
~Variables() {
for( Map::iterator it = myVariables.begin() ,
end = myVariables.end() ;
it != end ;
++ it ) {
delete it->second ;
}
}


Pourquoi appeler les destructeurs de chaque élément de la std::map
?

Son destructeur ne le fait pas ?


Le destructeur de map appelle les destructeurs des objets dans le map.
Ici, l'objet dans le map est un pointeur, et le destructeur d'un
pointeur ne fait pas grand chose. La logique ici veut qu'on appelle le
destructeur de l'objet pointé, et ça, le destructeur du map ne le
fait
pas automatiquement (et pour cause -- j'ai pas mal de map qui
contiennent des pointeurs à des objets statiques, par exemple).

[snip de la fin de la classe Variables]

void display( Variables const & vars ) {
int const * i = 0 ;
std::string const * s = 0 ;

vars.get( "integer" , i ) ;
vars.get( "string" , s ) ;

if ( i ) {
std::cout << "integer=" << * i << std::endl ;
}
else {
std::cout << "integer=<not set>" << std::endl ;
}
if ( s ) {
std::cout << "string=" << * s << std::endl ;
}
else {
std::cout << "string=<not set>" << std::endl ;
}
}


Le souci c'est qu'avec cette fonction il faut prévoir un cas par
type

intégré ou des conversions implicites sont-elles réalisées ?


Il faudrait que tu nous montres le cas qui t'inquiète. Il faut bien
savoir le type réel qu'on a quand on veut l'utiliser.

Ce n'est qu'un point de départ, il y a plusieurs variantes
possibles. Je dirais que c'est un exemple ou les Policy Classes
devraient être utile.


Je vais googler un coup sur "policy classes", merci pour le code.
Voilà ce que j'ai laborieusement pondu :


--8<-------------------------------------------------------------------


#include <iostream>
#include <map>

class untyped_value
{
public:
virtual ~untyped_value() {}
};

template<class T>
class typed_value : public untyped_value
{
public:
typed_value(const T& t) : value(t) {}
T& get_val() { return value; }
private:
T value;
};

class config_value
{
public:
config_value() : conf(0) {}
~config_value() { delete conf; }

template<class T> config_value(const T& t)
{
conf = new typed_value<T>(t);
}


Pourquoi pas d'initialisateur de membre :

template< typename T > config_value( T const& t )
: conf( new typed_value<T>( t )
{
}

Ici, ça ne fait vraiment pas de différence, mais AMHA, c'est une
bonne
habitude à prendre. (Si tu remplaces le pointeur brut par un auto_ptr,
comme je suggère plus bas, c'est même nécessaire

template<class T> void operator=(const T& t)
{
if(conf) delete conf;
conf = new typed_value<T>(t);
}


Qu'est-ce qui se passe dans le cas d'un affectation sur elle-même ici
?
Ou si le new lève une exception ?

Il y a plusieurs solutions. Elles ont toutes en commun que l'expression
new a terminée avant de commencer le delete ou de changer le pointeur
--
ni le delete ne la modification du pointeur ne doivent pouvoir lever
une
exception, et tant qu'on n'a commencé ni l'un ni l'autre, une
exception
ne fait pas mal.

La solution que je préfère ici, ça serait d'utiliser un
std::auto_ptr
comme membre. Ce qui donnerait :

template< typename T >
config_value& operator=( T const& t )
{
conf = std::auto_ptr< untyped_value >( new typed_value< T >( t
) ) ;
return *this ;
}

Tu rémarqueras aussi que mon operator= renvoie une référence à
l'objet.
C'est la convention, parce que c'est comme ça que fonctionne aussi
l'operator= de base.

template<class T> operator T()
{
return dynamic_cast<typed_value<T>*
(conf)->get_val();
}


Je ne suis pas sûr que j'ai compris. Si le dynamic_cast échoue, tu
utilises -> sur un pointeur nul (c'est un comportement indéfini) pour
appeler une fonction qui accéder à l'objet (ce qui provoquera un core
dump sur ma machine).

L'opérateur de conversion n'offre aucune possibilité à part une
exception pour signaler l'erreur. Dans ce cas-ci, et en supposant que
l'exception soit acceptable :

template< typename T > operator T() const
{
return dynamic_cast< typed_value< T >const& >( *conf
).get_val() ;
}

(Note aussi en passant le const sur la fonction.)

Dans le cas qui nous intéresse (données de configuration dont le type
a
déjà été vérifié), je crois tu pourrais même attrapper
l'exception et
avorter dans le cas d'erreur. Dans les utilisations plus générales,
en
révanche, il est probablement préférable de prévoir un moyen
immédiate
de signaler l'erreur -- dans un cas comme ceci, j'aime renvoyé un
pointeur, null si erreur, ce qui donnerait :

template< typename T > T const*
get() const
{
typed_value< T > const* tmp
= dynamic_cast< typed_value< T > const* >( conf.get() ) ;
return tmp == NULL
? NULL
: tmp->get_ptr() ;
}

private:
untyped_value* conf;


Comme j'ai dit ci-dessus :

std::auto_ptr< untyped_value > conf ;

};


--8<-------------------------------------------------------------------


En lisant les autres mails qui m'ont dirigé vers boost::any, je me
rends compte que j'ai presque réussi à réinventer la roue, mais
avec

des soucis pour la surcharge de "operator<<" (fonction non présente
dans ce code). Si je la déclare comme suit en tant que membre de la
classe config_value :

template<class T> std::ostream& operator(std::ostream& s);


Tu penses faire quoi avec cette déclaration ? Telle que tu l'as
écris,
elle ne doit même pas compiler.

Mais qu'importe. Dans les faits, il n'est pas possible de faire
l'opérateur << un membre, parce qu'en tant que membre, c'est le
paramètre à gauche qui a le type de la classe ; or, tu veux que le
paramètre à gauche soit std::ostream. Il faut passer par une fonction
globale -- soit friend, soit qui appelle des fonctions plubiques dans
ta
classe.

la fonction ne semble pas prise en compte lors de la résolution de
surcharge. Y a-t-il une raison à ça ?


En supposant que l'erreur de copie, c'est des << après le mot clé
operator, c'est normal. Elle ne serait pris en compte que si tu
écrivais
ma_variable << std::cout.

--
James Kanze GABI Software http://www.gabi-soft.fr
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


1 2