OVH Cloud OVH Cloud

[Debutant] Probleme de destruction

120 réponses
Avatar
Yoxoman
Bonjour à tous.

Je me permet de vous soumettre ce petit programme, qui représente en
gros la partie qui me pose problème d'un truc plus gros :

#include <iostream>

using namespace std;

class Vecteur
{
public:
double *val;
int taille;

Vecteur() {}
Vecteur(int i);
~Vecteur() {cout << "detruit" << endl; delete[] val;}

Vecteur foisDeux();
Vecteur &operator=(const Vecteur &v);
};

Vecteur::Vecteur(int i)
{
taille=i;
val=new double[i];
}

Vecteur Vecteur::foisDeux()
{
Vecteur temp(taille);

for (int i=0; i<taille; i++)
{
temp.val[i]=2*val[i];
}

return temp; //(1)
}

Vecteur &Vecteur::operator=(const Vecteur &v)
{
taille=v.taille;
val=new double[taille];

for (int i=0; i<taille; i++)
{
val[i]=v.val[i];
}

return *this;
}

int main(void)
{
Vecteur v1(3);
Vecteur v2;

v2=v1.foisDeux(); //(2)

return 0;
}

Segmentation fault.
J'ai tout d'abord eu du mal à identifier le problème, mais j'ai réussi
grâce à des supers pouvoirs hérités de Goldorak.
En fait, à la fin de la fonction foisDeux(), la variable temporaire
temp est détruite. Logique. En même temps, la fonction renvoie une
copie de l'objet, qui possède donc dans ses attributs un pointeur sur
une zone mémoire inaccessible (action du delete[]). Problème donc à la
destruction de ce dernier.

En fait, je ne sais pas trop comment arranger ce problème. Je ne
voudrais pas modifier *directement* les attributs de v1 dans la
fonction foisDeux(), pour garder ce vecteur en stock.

Merci d'avance pour votre aide.

10 réponses

Avatar
Luc Hermitte
Olivier Azeau wrote in
news:4L2vd.3783$:

Quand tu auras dépatouillé tout ça, le prochain problème qui devrait
survenir, ça sera probablement qqe chose comme :

Vecteur v(5);
v = v;


Tant qu'il travaille dans l'ordre (préparation d'un buffer temporaire
avant destruction/échange) il n'y a aucun problème.

[... snip test unitaire xxxCHECK_xxx(v = v)...]


Sur la façon d'éviter le problème, je préfère pour ma part rajouter
"if(&v == this) return *this;" qui est beaucoup plus explicite.


Je préfère bannir ce test, pessimiste, qui est susceptible de cacher des
problèmes plus profonds et sournois qu'il ne résoud nullement. En
revanche, je retiens l'idée du test unitaire (pour les classes,
encapsulant des ressources, qui ont une sémantique de valeur).

Ceci dit, il ne vérifie pas le problème des ressources manipulées de
manière non atomique. Je ne sais pas si il y a moyen de _localement_
surcharger l'allocation de ressource pour la forcer à planter dans le
cadre du test unitaire.


--
Luc Hermitte <hermitte at free.fr>
FAQ de <news:fr.comp.lang.c++> :
<http://www.cmla.ens-cachan.fr/Utilisateurs/dosreis/C++/FAQ/>
Dejanews : <http://groups.google.com/advanced_group_search>



Avatar
Gabriel Dos Reis
Olivier Azeau writes:

| Gabriel Dos Reis wrote:
| > Olivier Azeau writes:
| > | Mais tu fais quoi quand tu as une librairie tierce qui ne te
| > propose
| > | que des factories qui créent dans le tas et que tu n'as besoin de
| > | l'objet que le temps d'un appel de fonction ?
| > Mauvais fournisseur, changer de fournisseur. Ou, si la chose est
| > important pour le projet, investir dedans. Si ce n'est pas important
| > pour le projet, ...
| >
| Oui, c'est beau la théorie...

Je parle de la pratique et non une théorie. En fait, une pratique que
j'ai vécue plusisurs fois. Si la chose est importante pour le projet,
on y investit.
Mais je ne nie pas l'existance de mauvais managers, dans ce cas c'est
le manager qu'il faut blâmer.

-- Gaby
Avatar
Loïc Joly
Luc Hermitte wrote:
En
revanche, je retiens l'idée du test unitaire (pour les classes,
encapsulant des ressources, qui ont une sémantique de valeur).

Ceci dit, il ne vérifie pas le problème des ressources manipulées de
manière non atomique. Je ne sais pas si il y a moyen de _localement_
surcharger l'allocation de ressource pour la forcer à planter dans le
cadre du test unitaire.


Ca me fait penser que je n'ai pas encore vu dans les bibliothèques de
test unitaire de fonctions validant directement ce genre de chose. Genre
une fonction pour vérifier qu'une classe est non copiable, une autre qui
vérifie qu'elle est correctement copiable...

J'vais essayer d'y réfléchire.


--
Loïc

Avatar
Olivier Azeau
Luc Hermitte wrote:
Olivier Azeau wrote in
news:4L2vd.3783$:


Quand tu auras dépatouillé tout ça, le prochain problème qui devrait
survenir, ça sera probablement qqe chose comme :

Vecteur v(5);
v = v;


Tant qu'il travaille dans l'ordre (préparation d'un buffer temporaire
avant destruction/échange) il n'y a aucun problème.



[... snip test unitaire xxxCHECK_xxx(v = v)...]

Sur la façon d'éviter le problème, je préfère pour ma part rajouter
"if(&v == this) return *this;" qui est beaucoup plus explicite.



Je préfère bannir ce test, pessimiste, qui est susceptible de cacher des
problèmes plus profonds et sournois qu'il ne résoud nullement.


Tu veux dire que si tu as une classe qui alloue un buffer de n octets,
tu vas toujours coder l'operator= avec une copie du buffer alors qu'il
est si simple de vérifier qu'il n'y a strictement rien à faire ?




Avatar
kanze
Olivier Azeau wrote:
Luc Hermitte wrote:
Olivier Azeau wrote in
news:4L2vd.3783$:

Quand tu auras dépatouillé tout ça, le prochain problème qui
devrait





survenir, ça sera probablement qqe chose comme :

Vecteur v(5);
v = v;


Tant qu'il travaille dans l'ordre (préparation d'un buffer
temporaire avant destruction/échange) il n'y a aucun problème.


[... snip test unitaire xxxCHECK_xxx(v = v)...]

Sur la façon d'éviter le problème, je préfère pour ma part
rajouter



"if(&v == this) return *this;" qui est beaucoup plus explicite.


Je préfère bannir ce test, pessimiste, qui est susceptible de
cacher


des problèmes plus profonds et sournois qu'il ne résoud
nullement.



Tu veux dire que si tu as une classe qui alloue un buffer de n
octets,

tu vas toujours coder l'operator= avec une copie du buffer alors
qu'il

est si simple de vérifier qu'il n'y a strictement rien à faire ?


Il veut dire que si ton opérateur d'affectation est exception-safe, il
va prèsque sûrement fonctionner correctement dans le cas
d'affectation
sur lui-même, sans test particulier. Alors, le test est un comparison
de
plus ; c'est une optimisation si on affecte souvent à soi-même, mais
une
pessimisation dans les autres cas.

L'implémentation classique de son opérateur d'affectation, de
l'époque
d'avant les exceptions, aurait été :

if ( this != &other ) {
delete val ;
val = new double[ other.taille ] ;
taille = other.taille ;
std::copy( other.val, other.val + taille, val ) ;
memcpy( val, other.val, taille * sizeof( double ) ) ;
}
return *this ;

Le problème ici, c'est que s'il n'y a pas assez de mémoire, le new
double lève une exception, ce qui interrompt la fonction, et laisse
une
valeur invalide dans val. Il y a de nombreux solutions à ce problème
--
la plus simple, c'est de remplacer le new_handler avec une fonction qui
appelle abort:-). Mais la solution classique, et de loin la plus
générale, c'est de s'assurer que l'allocation et la copie aient lieu
avant de modifier l'objet en question. Si en plus on veut
réfactoriser
le code de l'affectation et du constructeur de copie, l'idiome de swap
est tout à fait indiqué :

Vector tmp( other ) ;
swap( tmp ) ;
return *this ;

avec une fonction de swap qui échange les pointeurs et les tailles,
tout
simplement (et donc, qui évite la copie profonde qui pourrait lever
une
exception. Mais évidemment, on pourrait aussi écrire :

double * newval = new double[ other.taille ] ;
std::copy( other.val, other.val + taille, newval ) ;
delete val ;
val = newval ;
taille = other.taille ;
return *this ;

L'essentiel, c'est simplement qu'on alloue le nouveau buffer avant de
libérer l'ancien. (L'allocation du nouveau buffer est la seule chose
ici
qui pourrait lever une exception, et donc interrompre la fonction.) Et
évidemment, si on alloue le nouveau buffer, et qu'on y a copié ce
qu'il
fallait, avant de supprimer l'ancien, l'affectation à soi-même marche
correctement d'office. On peut en ajouter un test, pour éviter
l'allocation et la copie, mais ce test est un test en plus dans le cas
où il n'y a pas affectation à soi-même.

Si on est concerné par la question de la vitesse, AMHA, la première
chose à faire serait de supprimer l'allocation si la destination a
déjà
la bonne taille, c-à-d quelque chose du genre :

double * newval = ( taille == other.taille
? val
: new double[ other.taille ] ) ;
std::copy( other.val, other.val + other.taille, newval ) ;
if ( newval != val ) {
delete [] val ;
}
val = newval ;
taille = other.taille ;
return *this ;

Si on est concerné par la taille, en revanche (et AMHA, c'est plus
probable que ce soit un problème), on pourrait vouloir éviter d'avoir
les deux buffers alloués à la fois. Dans ce cas-là, il faudrait
probablement une vérification de l'affectation à soi-même :

if ( this != &other ) {
delete val ;
val = new (nothrow) double[ other.taille ] ;
if ( val == NULL ) {
taille = 0 ;
} else {
std::copy( other.val, other.val + other.taille, val ) ;
taille = other.taille ;
}
}
return *this ;

Quelque chose de ce genre pourrait être intéressant si les Vecteur
sont
très, très grand.

--
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
kanze
Gabriel Dos Reis wrote:
Yoxoman writes:

| Je me permet de vous soumettre ce petit programme, qui représente
en

| gros la partie qui me pose problème d'un truc plus gros :

Si tu es débutant, comme tu l'indiques dans le sujet, je te
suggèrrerais de laisser de côté la gestion manuelle d'allocation
dynamique et d'y revenir bien plus tard, quand tu as compris
certaines

notions fondamentales de C++. En particuloer, je te suggèrerais
d'utiliser le type standard std:vector<int>.


Ça dépend du niveau de débuttant, je crois. Tôt ou tard, il va
falloir
qu'il abord de telles choses, s'il veut vraiment connaître le C++.
Même
si je suis bien d'accord avec toi que l'utilisation de std::vector et
de
std::string doivent venir bien avant.

À vrai dire, j'aurais mis une connaissance du modèle de l'objet C++,
avec ce que doit faire un constructeur de copie, un opérateur
d'affectation, etc., avant. Chose qui manifestement lui manquant, vu
les
problèmes dans son code. Mais qu'est-ce que tu suggères comme exemple
pour enseigner ces choses -- l'exemple classique, dans le temps, était
précisement une classe de String. Mais si on dit qu'il faut les
apprendre avant d'apprendre la gestion dynamique de la mémoire (et moi
aussi, je crois que c'est le cas), qu'est-ce qui sert d'exemple ?

--
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
kanze
Olivier Azeau wrote:
Gabriel Dos Reis wrote:
Olivier Azeau writes:
| Apprendre à se servir des templates et de la STL ? C'est ça les
| bases ?

Apprendre à se servir de la bilbiothèque standard. Oui. On n'a
besoin d'aucun talent particulier des templates.


Ouais... C'est avec des arguments comme ça que les auto-écoles vont
apprendre à faire les créneaux avec un radar de recul sous
prétexte

que c'est en "standard" sur les voitures.


Analogie, il me semble, serait plutôt des auto-écoles qui apprendre
à
démarrer la voiture, et de la faire avancer à la vitesse voulue en
ligne
droite, avant de apprendre à faire des créneaux en marche en
arrière.

En ce qui concerne ton analogie, elle a plusieurs défauts :

- Les radars de recul ne sont pas standard. Il existe bien des
voitures qui ne les ont pas. Tandis que std::vector et std::string
font bien partie de C++ -- s'ils ne sont pas présents, ce n'est
pas
du C++.

- Apprendre d'abord à faire des créneaux avec le radar de recul,
puis
ensuite sans, ne serait peut-être pas une mauvaise idée. Si on ne
l'a pas essayée encore, c'est sans doute que les radar de recul
sont
encore assez rare. Il n'est pas question que les élèves
n'apprenent
jamais des pointeurs. Seulement qu'ils les apprenent plus tard,
quand ils ont déjà maîtriser des choses plus simples.

J'ajouterai que cette philosophie n'est pas vraiment une innovation,
sauf dans la mésure qu'elle s'applique au C++ (et encore -- j'ai vu de
l'enseignement de C++ il y a 15 ans qui se servait des classes de
String
et d'Array « maison » pour éviter d'avoir à aborder les pointeurs
trop
tôt). C'est en fait l'ordre classique dans d'autres langages, comme
Pascal. (Voir l'ordre de présentation de Wirth, par exemple.) Je
dirais
qu'on enseigne le C++ comme ça, c'est une signe d'une certaine
maturité.

Cela n'a pas grand sens de se lancer dans la conception d'une
classe, quand on ne connaît pas les outils fondamentaux 101. Ah,
c'est vrai j'oubliais il y a encore un certain nombre de gens qui
pensent que C++ doit s'apprendre ou s'enseigner comme eux ils l'ont
appirs il y a plus d'une décennie, alors le langage d'aujourd'hui
est vachement différent. CIl ne reste plus qu'à espérer que la
promotion naturelle les sortira de la circulation, le plus
rapidement possible.


D'ailleurs nombre d'entre eux sont déjà sortis du dev C++ :
maintenant

ils ne font que recruter et superviser les développeurs...
Malheureusement, ils le font avec leurs propres critères et sont
souvent desespérés de voir le nombre de développeurs qui se
comportent

avec le C++ comme si c'était du Java...


Une partie du problème avec le Java comme langage d'enseignement,
c'est
précisement qu'en Java, il faut absolument aborder des pointeurs trop
tôt. Je crois (mais Gaby a plus d'expérience dans l'enseignement que
moi, et donc pourrait mieux dire) qu'il vaut mieux commencer par des
objets à sémantique de valeur -- dans une application complète, il
faut
évidemment les deux, mais j'ai l'impression qu'il y a quelque chose de
plus intuitif dans la sémantique de valeur, et qu'il vaut mieux
commencer par là.

--
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
kanze
Gabriel Dos Reis wrote:
Olivier Azeau writes:

[...]

| Un développeur qui connait pas trop mal Java et qui se met au C++
ça

| ressemble un peu à un qqun qui a appris à conduire avec une boite
| automatique et se met à conduire avec une boite manuelle : il
| connait le volant, l'accélérateur et le frein mais s'il reste en
| 1ère, il n'ira pas bien loin.

| C'est pour cela que je trouve dangereux d'apprendre le C++ avec
| toute la STL comme "faisant partie du langage". La STL est au C++,
| ce que le régulateur de vitesse automatique est à la conduite :
un

| fabuleux outil pour celui qui le maîtrise et un bon moyen de finir
| dans le décor pour les autres.

Proof by analogy is fraud.


Faute de prouver que l'analogie est valable. Si j'ai bien compris tes
propos, analogie serait plutôt que tu veux que l'auto-école enseigne
la
manipulation du volant, du levier de vitesse et de l'accelérateur
avant
d'enseigner les créneaux.

Comme nous allons le voir. Olivier nous énonce :

Un développeur qui connait pas trop mal Java et qui se met au
C++

ça ressemble un peu à un qqun qui a appris à conduire avec une
boite automatique et se met à conduire avec une boite manuelle :
il connait le volant, l'accélérateur et le frein mais s'il
reste

en 1ère, il n'ira pas bien loin.


Ce qui est un peu bizarre comme a propos, parce que s'il y a UN défaut
avec le Java comme langage d'enseignement, c'est qu'il impose une
connaissance des pointeurs bien trop tôt.

Et comme j'ai déjà dit par ailleurs, apprendre à conduire avec une
boîte
automatique avant de conduire avec une boîte manuelle n'est pas
forcement une mauvaise politique. Au moins il permet à l'élève de
n'attaquer qu'une chose à la fois. (Mais là, alors, c'est le C++ qui
fournit la boîte automatique, dans la forme des classes à sémantique
de
valeur.)

--
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
Luc Hermitte
wrote in
news::

Il veut dire que si ton opérateur d'affectation est exception-safe, il
va prèsque sûrement fonctionner correctement dans le cas d'affectation
sur lui-même, sans test particulier. Alors, le test est un comparison
de plus ; c'est une optimisation si on affecte souvent à soi-même,
mais une pessimisation dans les autres cas.


Oui.


<petite parenthèse>

Si on est concerné par la question de la vitesse, AMHA, la première
chose à faire serait de supprimer l'allocation si la destination a
déjà la bonne taille, c-à-d quelque chose du genre :

double * newval =
( taille == other.taille ? val : new double[ other.taille ] ) ;
std::copy( other.val, other.val + other.taille, newval ) ;
if ( newval != val ) delete [] val ;
val = newval ;
taille = other.taille ;
return *this ;


Dans ce cas particulier où l'on gère des doubles c'est OK.
Dans le cas plus générique où l'on pourrait avoir un vecteur de
n'importe quoi, et en particulier des choses dont les copies peuvent
lever des exceptions, cette méthode n'est plus bonne dans la mesure où
elle invaliderait l'objet que l'on tentait de modifer, si une exception
apparait durant le std::copy.

Mais en fait, je me demande si ces situations ne pourraient pas être
mieux traitées si l'on était capable de distinguer les objets temporaires
(comme ceux renvoyés par des fonctions ou des expressions
(mathématiques)) des objets "finaux" que l'on veut explicitement stocker.

Ce que je veux dire, c'est qu'avec une sémantique de déplacement, on
pourrait arriver à une situation où on n'aurait, je pense, plus besoin de
tester cette égalité des tailles.
Par exemple dans
for (....) { .... X = C + W * Y; ....}
Il n'y a même pas besoin de copier le résultat du temporaire vers X. Ici,
on devrait pouvoir se contenter d'échanger les buffers.

(chose que l'on retrouve dans les principales bibliothèques matricielles
qui existent déjà ; et l'article d'Andrei Alexandrescu sur MOJO traite de
cette sémantique de déplacement, mais j'ai oublié les détails)

</>


--
Luc Hermitte <hermitte at free.fr>
FAQ de <news:fr.comp.lang.c++> :
<http://www.cmla.ens-cachan.fr/Utilisateurs/dosreis/C++/FAQ/>
Dejanews : <http://groups.google.com/advanced_group_search>

Avatar
Alain Naigeon
a écrit dans le message news:


Et comme j'ai déjà dit par ailleurs, apprendre à conduire avec une
boîte
automatique avant de conduire avec une boîte manuelle n'est pas
forcement une mauvaise politique.

*pas forcément*, ok, voilà une expression méritoire et nécessaire !
J'ai l'impression qu'aux USA on est absolument convaincu qu'on
ne peut s'en sortir avec une boîte manuelle, et ça n'empêche pas
des centaines de millions d'Européens de conduire ainsi tous les
jours.

Il y a une vingtaine d'années, un ministre des transports (par ailleurs
plutôt bon) avait mandaté une commission d'experts pour voir si
on pouvait décider l'obligation, en ville, de rouler le soir avec les
feux de croisement au lieu des simples feux de position. Ces doctes
experts avaient conclu que c'était déraisonnable. Or il se trouve :
- que la tradition, depuis toujours en Alsace, était de rouler ainsi !
- qu'aujourd'hui il est question d'imposer cela *en plein jour* !
Comme quoi certaines "preuves", quoique non analogiques, sont
des doubles fraudes (personnellement j'emploierais plutôt "erreurs",
qui est moins blessant - est-ce que dialoguer avec quelqu'un qu'on
croit de mauvaise foi, ce n'est pas manifester qu'on aime perdre
son temps ? ;-) )

Lors d'un séjour au Canada, j'avais remarqué des dizaines de feux
"stop" allumés devant moi dans les files d'attentes aux feux rouges.
J'ai alors fait le lien avec une adecdote survenue quand j'ai loué la
voiture ; pendant les quelques explications reçues justement sur la
boîte automatique, j'ai demandé au gars à quoi servait la position
"0" (le point mort, vu après coup dans une notice de la boîte à gants) ;
il m'a répondu "à rien" :-)

A peu de choses près ce qu'on m'avait répondu pour le pointeurs
il y a quelque temps (ok, pour un débutant... mais quand même !).
A ce sujet, et dans ce contexte, me permettrai-je d'ajouter que
répondre "prôner l'étude des pointeurs pour apprendre le C++
d'aujourd'hui, c'est comme si on demandait d'étudier l'assembleur
pour faire du C", voilà exactement un raisonnement par analogie !

(ce n'était pas une réponse de James, qui, je crois, a une position
plus nuancée, c'était une réponse de Gaby, entre autres - l'argu-
ment est revenu plus d'une fois. Quoiqu'il en soit, sur usenet en
général, et sur ce groupe, je réagis à des idées, pas à des personnes).

--

Français *==> "Musique renaissance" <==* English
midi - facsimiles - ligatures - mensuration
http://anaigeon.free.fr | http://www.medieval.org/emfaq/anaigeon/
Alain Naigeon - - Strasbourg, France