Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

std::vector d'une classe sans constructeur par defaut

10 réponses
Avatar
jeremie fouche
Bonsoir

Ceci est la suite (et fin j'espère) du fil [Quel conteneur choisir].

Voici ce que j'ai :

class Attribute
{
public:
Attribute(const std::string& val, const Config& rcConf)
: m_sValue(val), m_rcConf(rcConf) {}

std::string m_sValue;
const Config& m_rcConf;
};

typedef std::vector<Attribute> TAttributes;

Comme je n'ai pas de constructeur par défaut (impossible a cause du
membre m_rcConf qui est une référence, je ne vois pas comment utiliser
le conteneur std::vector<>.
Avec std::map<>, je n'avais pas le problème, car la méthode insert ne
crée pas d'abord l'élément dans la map grâce au constructeur par défaut.

Ca m'ennuie de transformer ma référence en pointeur :

class Attribute
{
public:
Attribute(const std::string& val = "", const Config* pConf = NULL)
: m_sValue(val), m_rcConf(rcConf) {}

std::string m_sValue;
const Config* m_pcConf;
};

Bien sur, je peux utiliser std::list<>, qui me permettra de parer le
problème, mais je me disais que c'était l'occasion de /jouer/ avec
std::vector que je n'ai pas l'habitude d'utiliser (contrairement a
beaucoup d'entre vous qui, d'apres ce que j'ai compris, utilisaient
souvent std::vector<> comme conteneur par defaut).

Y a t il une solution avec std::vector<> ?

Merci
--
Jérémie

10 réponses

Avatar
James Kanze
On Jun 17, 8:01 pm, jeremie fouche wrote:
Ceci est la suite (et fin j'espère) du fil [Quel conteneur choisir].

Voici ce que j'ai :

class Attribute
{
public:
Attribute(const std::string& val, const Config& rcConf)
: m_sValue(val), m_rcConf(rcConf) {}

std::string m_sValue;
const Config& m_rcConf;
};

typedef std::vector<Attribute> TAttributes;

Comme je n'ai pas de constructeur par défaut (impossible a cause du
membre m_rcConf qui est une référence, je ne vois pas comment utiliser
le conteneur std::vector<>.


Le problème ici, ce n'est pas l'absence d'un constructeur par
défaut (vector n'en a pas besoin), mais d'un opérateur
d'affectation (dont ont besoin toutes les collections).

Avec std::map<>, je n'avais pas le problème, car la méthode
insert ne crée pas d'abord l'élément dans la map grâce au
constructeur par défaut.


La fonction operator[], en revanche, si.

Ca m'ennuie de transformer ma référence en pointeur :

class Attribute
{
public:
Attribute(const std::string& val = "", const Config* pConf = NULL)
: m_sValue(val), m_rcConf(rcConf) {}

std::string m_sValue;
const Config* m_pcConf;
};

Bien sur, je peux utiliser std::list<>, qui me permettra de parer le
problème, mais je me disais que c'était l'occasion de /jouer/ avec
std::vector que je n'ai pas l'habitude d'utiliser (contrairement a
beaucoup d'entre vous qui, d'apres ce que j'ai compris, utilisaient
souvent std::vector<> comme conteneur par defaut).


Ça ne marche pas mieux avec std::list. G++, au moins, râle qu'il
manque un opérateur d'affectation dans les deux cas. Alors, des
deux choses une : ou bien, tu utilises un pointeur, pour que le
compilateur puisse t'en génère un, ou bien tu en définis un
selon ton gré. (Ne pas le fournir, c'est un comportement
indéfini. Ce qui veut dire que parfois, ça pourrait donner l'air
de marcher.)

Y a t il une solution avec std::vector<> ?


La même qu'avec std::list.

--
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
jeremie fouche

Bonsoir

On Jun 17, 8:01 pm, jeremie fouche wrote:
Ceci est la suite (et fin j'espère) du fil [Quel conteneur choisir].

Voici ce que j'ai :

class Attribute
{
public:
Attribute(const std::string& val, const Config& rcConf)
: m_sValue(val), m_rcConf(rcConf) {}

std::string m_sValue;
const Config& m_rcConf;
};

typedef std::vector<Attribute> TAttributes;

Comme je n'ai pas de constructeur par défaut (impossible a cause du
membre m_rcConf qui est une référence, je ne vois pas comment utiliser
le conteneur std::vector<>.


Le problème ici, ce n'est pas l'absence d'un constructeur par
défaut (vector n'en a pas besoin), mais d'un opérateur
d'affectation (dont ont besoin toutes les collections).


J'avais donc mal compris le problème...
Arrêtes moi si je me trompe : Il me semblais que l'opérateur
d'affectation était généré automatiquement par le compilateur, en
effectuant l'opérateur d'affectation pour chaque membre de la classe. En
revanche, cela ne semble pas posé de problème a priori :
- operateur = est défini pour std::string
- operateur = me semble trivial pour une référence, donc générable
automatiquement par le compilateur.

Donc si j'ai bien compris, il suffirait que j'implemente l'operateur = ?
(non testé)

class Attribute
{
public:
Attribute(const std::string& val, const Config& rcConf)
: m_sValue(val), m_rcConf(rcConf) {}

Attribute& operateur = (const Attribute& rcAttribute)
{
if (this != &rcAttribute)
{
sValue = rcAttribute.sValue;
m_rcConf = rcAttribute.m_rcConf;
}
return *this
}

std::string m_sValue;
const Config& m_rcConf;
};

Mais il me semblait que c'était le code généré automatiquement par le
compilateur.

Avec std::map<>, je n'avais pas le problème, car la méthode
insert ne crée pas d'abord l'élément dans la map grâce au
constructeur par défaut.


La fonction operator[], en revanche, si.


Effectivement, c'est la raison pour laquelle j'utilisais la méthode
insert().

Bien sur, je peux utiliser std::list<>, qui me permettra de parer le
problème, mais je me disais que c'était l'occasion de /jouer/ avec
std::vector que je n'ai pas l'habitude d'utiliser (contrairement a
beaucoup d'entre vous qui, d'apres ce que j'ai compris, utilisaient
souvent std::vector<> comme conteneur par defaut).


Ça ne marche pas mieux avec std::list. G++, au moins, râle qu'il
manque un opérateur d'affectation dans les deux cas.


C'est surprenant, mon g++ (3.4.2) sous MinGW ne me dit rien... J'ai
juste transformé mon typedef std::vector<Attribute> en
std::list<Attribute>, et tout fonctionne comme sur des roulettes.


--
Jérémie


Avatar
Fabien LE LEZ
On Sun, 17 Jun 2007 23:02:02 +0200, jeremie fouche :

- operateur = me semble trivial pour une référence


Pas du tout. Il est impossible de modifier une référence.
En général, si une classe contient une référence, l'affectation n'est
pas possible.
Pour pouvoir définir un opérateur d'affectation, tu voudras
probablement remplacer ta référence par un pointeur.

Avatar
jeremie fouche
On Sun, 17 Jun 2007 23:02:02 +0200, jeremie fouche :

- operateur = me semble trivial pour une référence


Pas du tout. Il est impossible de modifier une référence.
En général, si une classe contient une référence, l'affectation n'est
pas possible.
Pour pouvoir définir un opérateur d'affectation, tu voudras
probablement remplacer ta référence par un pointeur.



OK

Je suis tout de meme un peu perdu, car l'affectation fonctionne
parfaitement dans du code :

const Attribute& rcAttribute = (*iteratorSurVectorAttribute);

C'est quoi la différence ?
Et pourquoi ca marche avec le std::list<> ?
Me voili tout chamboulé dans ma tête !

--
Jérémie


Avatar
Fabien LE LEZ
On Sun, 17 Jun 2007 23:11:43 +0200, jeremie fouche :

Je suis tout de meme un peu perdu, car l'affectation fonctionne
parfaitement dans du code :

const Attribute& rcAttribute = (*iteratorSurVectorAttribute);


Ceci n'est pas une affectation, c'est une initialisation.

int a (12); -> constructeur de copie
int a= 12; -> même chose

int a; -> constructeur par défaut
a= 12; -> affectation

Et pourquoi ca marche avec le std::list<> ?


Si le standard indique qu'un bout de code a un "comportement
indéfini", tout peut arriver. Le compilateur peut refuser le code ; ou
bien, le code peut compiler, mais planter une fois sur deux, ou (plus
gênant), fonctionner parfaitement sur ta machine, mais pas sur la
machine du client, etc.

Avatar
jeremie fouche
On Sun, 17 Jun 2007 23:11:43 +0200, jeremie fouche :

Je suis tout de meme un peu perdu, car l'affectation fonctionne
parfaitement dans du code :

const Attribute& rcAttribute = (*iteratorSurVectorAttribute);


Ceci n'est pas une affectation, c'est une initialisation.

int a (12); -> constructeur de copie
int a= 12; -> même chose

int a; -> constructeur par défaut
a= 12; -> affectation


Je suis un imbécile. On ne peut assigner une référence qu'à sa
construction, toutes les affectations suivantes modifient le contenu de
la référence, mais pas la référence elle même.

Et pourquoi ca marche avec le std::list<> ?


Si le standard indique qu'un bout de code a un "comportement
indéfini", tout peut arriver. Le compilateur peut refuser le code ; ou
bien, le code peut compiler, mais planter une fois sur deux, ou (plus
gênant), fonctionner parfaitement sur ta machine, mais pas sur la
machine du client, etc.


OK.
J'ai regardé l'implémentation de std::list<> sur ma machine :

void
push_back(const value_type& __x)
{
this->_M_insert(end(), __x);
}

void
_M_insert(iterator __position, const value_type& __x)
{
_Node* __tmp = _M_create_node(__x);
__tmp->hook(__position._M_node);
}

_Node*
_M_create_node(const value_type& __x)
{
_Node* __p = this->_M_get_node();
try
{
std::_Construct(&__p->_M_data, __x);
}
catch(...)
{
_M_put_node(__p);
__throw_exception_again;
}
return __p;
}

template<typename _T1, typename _T2>
inline void
_Construct(_T1* __p, const _T2& __value)
{
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 402. wrong new expression in [some_]allocator::construct
::new(static_cast<void*>(__p)) _T1(__value);
}

Et dans ce cadre, (même si je ne comprend pas très bien le new effectué
dans _Construct, on voit tout de même l'utilisation d'un constructeur
par recopie, qui lui est trivial. C'est donc la raison du fonctionnement
sur ma machine.

Pour terminer, faut il que je change ce que j'ai actuellement ? Car ce
logiciel DOIT etre multi-plateforme, donc compilé sur PC, linux, MAC. Et
pour information, ou peux tu voir que le standard indique qu'un bout de
code a un "comportement indéfini" ?

Merci beaucoup a toi et James pour m'avoir remis (en parti) les idées au
clair.

--
Jérémie


Avatar
James Kanze
On Jun 17, 11:02 pm, jeremie fouche wrote:
On Jun 17, 8:01 pm, jeremie fouche wrote:
Ceci est la suite (et fin j'espère) du fil [Quel conteneur choisir].

Voici ce que j'ai :

class Attribute
{
public:
Attribute(const std::string& val, const Config& rcConf)
: m_sValue(val), m_rcConf(rcConf) {}

std::string m_sValue;
const Config& m_rcConf;
};

typedef std::vector<Attribute> TAttributes;

Comme je n'ai pas de constructeur par défaut (impossible a cause du
membre m_rcConf qui est une référence, je ne vois pas comment util iser
le conteneur std::vector<>.


Le problème ici, ce n'est pas l'absence d'un constructeur par
défaut (vector n'en a pas besoin), mais d'un opérateur
d'affectation (dont ont besoin toutes les collections).


J'avais donc mal compris le problème...
Arrêtes moi si je me trompe : Il me semblais que l'opérateur
d'affectation était généré automatiquement par le compilateur, en
effectuant l'opérateur d'affectation pour chaque membre de la classe. En
revanche, cela ne semble pas posé de problème a priori :
- operateur = est défini pour std::string
- operateur = me semble trivial pour une référence, donc généra ble
automatiquement par le compilateur.


La spécification du langage dit que c'est une erreur si le
compilateur a besoin de générer l'implémentation de l'operator=
et la classe contient une référence. C'est vrai que
l'affectation à travers une référence est bien définie. Mais si
tu t'es servi d'une référence, et non d'une valeur, on suppose
que c'est parce que tu voulais la référence, et pas la même
sémantique que si l'élément faisait physiquement partie de
l'objet.

Donc si j'ai bien compris, il suffirait que j'implemente
l'operateur = ? (non testé)

class Attribute
{
public:
Attribute(const std::string& val, const Config& rcConf)
: m_sValue(val), m_rcConf(rcConf) {}

Attribute& operateur = (const Attribute& rcAttribute)
{
if (this != &rcAttribute)
{
sValue = rcAttribute.sValue;
m_rcConf = rcAttribute.m_rcConf;


Est-ce que cette ligne fait réelement ce que tu veux ? Et si
oui, pourquoi la référence ?

}
return *this
}

std::string m_sValue;
const Config& m_rcConf;
};

Mais il me semblait que c'était le code généré automatiquement
par le compilateur.


Le compilateur ne génère rien dans ce cas-ci.

D'après ma propre expérience, des références sont assez rare
comme membre, et typiquement elles ne servent que pour des
choses qui ne font pas partie de la représentation valeur de
l'objet. Et ne sont pas copiées lors d'une affectation.

Avec std::map<>, je n'avais pas le problème, car la méthode
insert ne crée pas d'abord l'élément dans la map grâce au
constructeur par défaut.


La fonction operator[], en revanche, si.


Effectivement, c'est la raison pour laquelle j'utilisais la
méthode insert().

Bien sur, je peux utiliser std::list<>, qui me permettra de parer le
problème, mais je me disais que c'était l'occasion de /jouer/ avec
std::vector que je n'ai pas l'habitude d'utiliser (contrairement a
beaucoup d'entre vous qui, d'apres ce que j'ai compris, utilisaient
souvent std::vector<> comme conteneur par defaut).


Ça ne marche pas mieux avec std::list. G++, au moins, râle qu'il
manque un opérateur d'affectation dans les deux cas.


C'est surprenant, mon g++ (3.4.2) sous MinGW ne me dit rien... J'ai
juste transformé mon typedef std::vector<Attribute> en
std::list<Attribute>, et tout fonctionne comme sur des roulettes.


Les vérifications de ce genre n'ont été introduites qu'à partir
de 4.0, il me semble, et ont besoin des options spéciales pour
les activer.

D'après ce que j'ai compris, en revanche, on va ajouter du
support amélioré pour ce genre de vérifications, et on va les
rendre obligatoire dans la prochaine version de la norme. (En
revanche, je crois aussi qu'on va relacher un peu les
exigeances. Pour permettre une std::list même sans
l'affectation, par exemple.)

--
James Kanze (GABI Software, from CAI) 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
James Kanze
On Jun 18, 8:22 am, jeremie fouche wrote:

[...]
Pour terminer, faut il que je change ce que j'ai actuellement ?


À ta place, je reflechirais sur ce que je veux comme sémantique.
Parce que le nom de ta référence avait quelque chose à faire
avec « Config », d'après ce dont je me souviens. Et les
config, c'est souvent l'exception où une référence convient,
parce qu'en général, elle ne fait pas partie de la
représentation valeur de l'objet ; on la fixe lors de la
création, et on ne la change pas, même quand on change la
valeur, suite à une affectation. Alors, on implémente soi-même
l'opérateur d'affectation, sans toucher à l'élément.

Car ce logiciel DOIT etre multi-plateforme, donc compilé sur
PC, linux, MAC. Et pour information, ou peux tu voir que le
standard indique qu'un bout de code a un "comportement
indéfini" ?


Dans la norme. Dans ce cas-ci, dans la section sur la
bibliothèque en général (§17.4.3.6), elle dit « In particular,
the effects are undefined in the following cases: [...]-- for
types used as template arguments when instantiating a template
component, if the operations on the type do not implement the
semantics of the applicable Requirements subclause." Dans la
version actuelle de la norme, il y a une section commune à
toutes les collections qui précise que le type contenu doit être
"CopyConstructible" et "Assignable". (Dans la proposition de la
prochaine version, ces exigeances sont réparties à chaque
collection, et on a laissé tomber "Assignable" pour std::list,
parce qu'en fait, c'est une collection à base de noeuds, et des
collections à base de noeuds n'ont pas besoin de l'affectation
dans leur implémentation.)

--
James Kanze (GABI Software, from CAI) 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
Loïc Joly

D'après ma propre expérience, des références sont assez rare
comme membre, et typiquement elles ne servent que pour des
choses qui ne font pas partie de la représentation valeur de
l'objet. Et ne sont pas copiées lors d'une affectation.


Typiquement, dans mes programmes, quand j'ai des références en variable
membre, c'est assez souvent dans l'implémentation du RAII, ce qui entre
dans tes critères. A part ça, c'est une chose qui m'arrive assez rarement.


--
Loïc

Avatar
James Kanze
On Jun 18, 8:15 pm, Loïc Joly
wrote:

D'après ma propre expérience, des références sont assez rare
comme membre, et typiquement elles ne servent que pour des
choses qui ne font pas partie de la représentation valeur de
l'objet. Et ne sont pas copiées lors d'une affectation.


Typiquement, dans mes programmes, quand j'ai des références en variab le
membre, c'est assez souvent dans l'implémentation du RAII, ce qui entre
dans tes critères. A part ça, c'est une chose qui m'arrive assez rare ment.


En effet. À vrai dire, je pensais surtout à des classes de
valeur. On hésite moins à les utiliser dans les classes qui
supporte pas l'affectation -- c'est le cas des implémentations
du RAII, mais aussi de certains types entité.

--
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