Mon filtrage ne fonctionne pas

Résolu
pgl10 Messages postés 380 Date d'inscription samedi 18 décembre 2004 Statut Membre Dernière intervention 29 octobre 2023 - 31 août 2015 à 11:52
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 - 31 août 2015 à 18:40
Bonjour à tous,
J'ai du mal à comprendre ce qui se passe dans le logiciel suivant :


#include <iostream>

class Vec3 {
    public:
    Vec3() : _x(0.0), _y(0.0), _z(0.0) {};
    Vec3(double a) : _x(a), _y(a), _z(a) {};
    Vec3(double a, double b, double c) : _x(a), _y(b), _z(c) {};
    double operator [] (int i) const {
        if(i<0) return 0.0;
        if(i>2) return 0.0;
        return (&_x)[i];
    }
    double& operator [] (int i) {
        static double zero = 0.0;
        if(i<0) return zero;
        if(i>2) return zero;
        return (&_x)[i];
    }
    private:
    double _x, _y, _z;
};
int main () {
    Vec3 v(1.1, 1.2, 1.3);
    std::cout << "v[0] : " << v[0] << std::endl;
    std::cout << "v[1] : " << v[1] << std::endl;
    std::cout << "v[2] : " << v[2] << std::endl;
    std::cout << std::endl;
    v[0] = 2.1;
    v[1] = 2.2;
    v[2] = 2.3;
    std::cout << "v[0] : " << v[0] << std::endl;
    std::cout << "v[1] : " << v[1] << std::endl;
    std::cout << "v[2] : " << v[2] << std::endl;
    std::cout << std::endl;
    v[3] = 3.4;
    double v3 = v[3];
    std::cout << "v[3] : " << v[3] << std::endl;
    std::cout << " v3  : " <<  v3  << std::endl;
    std::cout << "v[4] : " << v[4] << std::endl;
    std::cout << std::endl;
    return 0;
}

Je voulais remplacer les fonctions habituelles du genre :
v.setx(a) et/ou x+v.getx()
par simplement : v[0]=a et x+v[0]
Et cela fonctionne très bien tant que l'indice utilisé est compris entre 0 et 2 comme cela doit être. Mais j'ai aussi voulu contrôler l'éventualité inadaptée d'un indice inapproprié. C'est pourquoi j'ai mis les instructions if(i<0)... et if(i>2)... dans les deux fonctions concernées. J'obtiens à l'exécution ceci :

v[0] : 1.1
v[1] : 1.2
v[2] : 1.3

v[0] : 2.1
v[1] : 2.2
v[2] : 2.3

v[3] : 3.4
 v3  : 3.4
v[4] : 3.4


Pourquoi l'instruction v3=v[3]; trouve quand même la valeur 3.4 et pourquoi l'affichage de v[4] donne lui aussi la valeur 3.4 ? D'où cela vient-il ?
J'utilise Visual Studio 2012 Express for Windows Desktop en français, mais j'ai fait une compilation C++ en ligne avec gcc et cela donne la même chose. Est-ce que l'instruction v[3]=3.4; range quand même quelque chose ? et où ? On dirait que le filtrage des mauvaises valeurs du rang utilisé ne fonctionne pas.



--

5 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 31/08/2015 à 12:59
Bonjour.

C'est parce que tu écrases la valeur de "zero", en faisant "v[3] = 3.4;". En effet, en faisant v[3] , tu fais un v.operator[](3) qui retourne *l'adresse* de "zero", que tu met à 3.4. Tu peux t'en apercevoir en faisant:
  double& operator [] (int i)
  {
    static double zero = 0.0;
    std::cout << "call& " << i << "zero = " << zero << std::endl;

    if(i<0) return zero;
    if(i>2) return zero;
    return (&_x)[i];
  }


D'un point de vue technique, je comprends ce que tu veux faire, mais c'est très "bancal". Tu pars du principe que x, y, z seront contigües en mémoire naturellement (alors que ça dépend de l'implémentation de ton compilateur). Mieux vaut s'en assurer en utilisant un mécanisme adapté (une "union"):

En C++11 (je conseille la version C++11 qui est maintenant considérée comme la "dernière"):
#include <iostream>
#include <cassert>

union Vec3
{
public:
  Vec3()
    : _coord{0, 0, 0}
  {
  }

  Vec3(double x)
    : _coord{x, x, x}
  {
  }

  Vec3(double x, double y, double z)
    : _coord{x, y, z}
  {
  }

  double operator[](int i) const
  {
    assert(i >= 0);
    assert(i <= 2);

    return _tab[i];
  }

  double& operator [] (int i)
  {
    assert(i >= 0);
    assert(i <= 2);

    return _tab[i];
  }

private:
  double _tab[3];

  struct Coord
  {
    double x;
    double y;
    double z;
  } _coord;
};

int main ()
{
  Vec3 v(1.1, 1.2, 1.3);
  std::cout << "v[0] : " << v[0] << std::endl;
  std::cout << "v[1] : " << v[1] << std::endl;
  std::cout << "v[2] : " << v[2] << std::endl;
  std::cout << std::endl;
  v[0] = 2.1;
  v[1] = 2.2;
  v[2] = 2.3;
  std::cout << "v[0] : " << v[0] << std::endl;
  std::cout << "v[1] : " << v[1] << std::endl;
  std::cout << "v[2] : " << v[2] << std::endl;
  std::cout << std::endl;
  v[3] = 3.4;
  double v3 = v[3];
  std::cout << "v[3] : " << v[3] << std::endl;
  std::cout << " v3  : " <<  v3  << std::endl;
  std::cout << "v[4] : " << v[4] << std::endl;
  std::cout << std::endl;

  return 0;
}


En C++03, c'est un peu moins classe, il faudra changer ceci pour que ça fonctionne:
  Vec3()
  {
    _coord.x = 0;
    _coord.y = 0;
    _coord.z = 0;
  }

  Vec3(double x)
  {
    _coord.x = x;
    _coord.y = x;
    _coord.z = x;
  }

  Vec3(double x, double y, double z)
  {
    _coord.x = x;
    _coord.y = y;
    _coord.z = z;
  }


J'ai aussi ajouté l'utilisation d'un assert, plus sûr que de retourner un numéro valide (ou une adresse hasardeuse).


Améliorer votre expérience CodeS-SourceS avec ce plugin:
ttp://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
pgl10 Messages postés 380 Date d'inscription samedi 18 décembre 2004 Statut Membre Dernière intervention 29 octobre 2023 11
31 août 2015 à 15:36
Bonjour CptPingu,
Bravo et merci beaucoup pour cette explication très claire. Oui, c'est vrai que je supposais que les variables _x, _y et _z étaient contigües. Toutes mes questions du message initial ont maintenant leurs réponses. Et zero contenait la valeur 3.4 bien sûr ! J'ai donc modifié mon exemple comme suit :


#include <iostream>
#include <cassert>

class Vec3 {
    public:
    Vec3() {
        _coord._suite.x = 0.0;
        _coord._suite.y = 0.0;
        _coord._suite.z = 0.0;
    }
    Vec3(double a) {
        _coord._suite.x = a;
        _coord._suite.y = a;
        _coord._suite.z = a;
    }
    Vec3(double a, double b, double c) {
        _coord._suite.x = a;
        _coord._suite.y = b;
        _coord._suite.z = c;
    }
    double operator[](int i) const {
        assert(i >= 0);
        assert(i <= 2);
        return _coord._tab[i];
    }
    double& operator [] (int i) {
        assert(i >= 0);
        assert(i <= 2);
        return _coord._tab[i];
    }
    private:
    union {
        double _tab[3];
        struct {
            double x;
            double y;
            double z;
        } _suite;
    } _coord;
};

int main () {
    Vec3 v(1.1, 1.2, 1.3);
    std::cout << "v[0] : " << v[0] << std::endl;
    std::cout << "v[1] : " << v[1] << std::endl;
    std::cout << "v[2] : " << v[2] << std::endl;
    std::cout << std::endl;
    v[0] = 2.1;
    v[1] = 2.2;
    v[2] = 2.3;
    std::cout << "v[0] : " << v[0] << std::endl;
    std::cout << "v[1] : " << v[1] << std::endl;
    std::cout << "v[2] : " << v[2] << std::endl;
    std::cout << std::endl;
    v[3] = 3.4;
    double v3 = v[3];
    std::cout << "v[3] : " << v[3] << std::endl;
    std::cout << " v3  : " <<  v3  << std::endl;
    std::cout << "v[4] : " << v[4] << std::endl;
    std::cout << std::endl;
    return 0;
}

Et j'obtiens le résultat suivant :

v[0] : 1.1
v[1] : 1.2
v[2] : 1.3

v[0] : 2.1
v[1] : 2.2
v[2] : 2.3

v[3] : 3.4
 v3  : 3.4
v[4] : 8.59626e-306

Il n'y a pas le moindre message d'erreur à l'exécution. On peut noter v[4] ne renvoie plus la même valeur que v[3], ce qui est évident dans cette nouvelle version. Mais v[3] et v[4] qui sont inappropriés auraient dus être signalés comme une erreur, en particulier : v[3]=3.4; est justement ce que l'on refuse d'accepter. Remerciements, pgl10
0
pgl10 Messages postés 380 Date d'inscription samedi 18 décembre 2004 Statut Membre Dernière intervention 29 octobre 2023 11
31 août 2015 à 16:50
J'ai répondu un peu vite ! La solution proposée par CptPingu fonctionne parfaitement en mode Debug. Par contre, en mode Release les instructions assert() sont ignorées dans le système que j'utilise.
0
pgl10 Messages postés 380 Date d'inscription samedi 18 décembre 2004 Statut Membre Dernière intervention 29 octobre 2023 11
Modifié par pgl10 le 31/08/2015 à 17:09
En plus, il est possible de modifier les options de la compilation pour conserver l'emploi des instructions assert() même en mode Release. Je l'ai fait. Cela fonctionne parfaitement bien. Merci CptPingu.

Au lieu d'avoir les habituelles fonctions setx(a), sety(a), setz(a), getx(), gety() et getz() l'exemple ci-dessus permet de faire le même traitement en écrivant plus simplement, par exemple :
Vec3 v;
v[0] = 1.0; // au lieu de : v.setx(1.0);
double d = v[1]; // au lieu de : double d = v.gety();
C'était l'objectif recherché. A chacun de voir si cela convient mieux ou pas.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
31 août 2015 à 18:40
Au sujet des "assert". Ceux-ci sont là pour avoir le "beurre et l'argent du beurre". Je m'explique. Ce sont des "barrières de sécurité", qui permettent de faire "planter" le code volontairement. Ce qu'on met en assert, n'est pas ce que l'on met en tant qu'erreur, mais ce qui ne devrait arriver sous aucun prétexte.
Exemple:
Le fichier n'est pas trouvé => erreur "normale", que l'on peut renvoyer à l'utilisateur (code de retour ou exception).
Dépassement de borne => assert, car c'est une erreur de "code" pas d'utilisation du programme (un bug détectable par le programmeur, et donc un message destiné à un programmeur et non à un utilisateur).

Le souci, c'est que faire de nombreux "check" pour blinder un maximum le code, coûte cher. C'est la raison pour laquelle ils ne sont pas présents en "release". En théorie, il faut toujours compiler en debug, puis une fois le code bien stable, le compiler en release.

Attention, activer les assert en "release", expose plus grandement au problème suivant:
Nombreux sont ceux (moi le premier) qui se sont fait avoir par une subtilité de ce mécanisme. En effet, en release, les assert sont désactivés de manière "textuelle". C'est-à-dire que la ligne est effacée.

Imaginons le cas suivant qui fonctionnerait en debug, mais pas en release:
int main()
{
  int i = 0;
  assert(++i == 1);
  std::cout << "Devrait être 1 == " << i << std::endl;
  return 0;
}


En debug, on verrait "1 == 1", mais en release on verrait "0 == 1". Il ne faut donc jamais changer l'état d'une variable dans un assert.

Activer les assert en release est donc déconseillé pour deux raisons:
- Quelqu'un qui reprend le code, ou un morceau de code avec un assert qui change un état (un oubli ou un bug à ce niveau là est vite arrivé), ne comprendra pas pourquoi ça fonctionne dans le code original (c'est très rare d'activer les assert en release).
- Les checks supplémentaires peuvent coûter cher (raison pour laquelle dans la STL, le std::vector ne vérifie les bornes qu'en debug).

Je conseille donc de coder en debug, puis de tester de temps en temps en release sans assert pour s'assurer que tout fonctionne bien (et tester les perfs, bien évidemment).


Au sujet du C++11: Je conseille aussi l'utilisation du C++11 au lieu du C++ classique. Pour plusieurs raisons:
- On n'est pas obligé d'utiliser les nouvelles fonctionnalités (on peut coder comme avant).
- Les gains en performances peuvent être significatifs (sans rien changer au code).
- C'est très facile à faire. Avec gcc, il suffit juste d'ajouter l'option: --std=c++11
- On profite du mot clé "auto" et de la boucle for étendu (ou foreach), bien pratique, ainsi que d'une STL plus fournie.
Exemple:
std::vector<int> tab;

// C++03
std::vector<int>::const_iterator end = tab.end();
for (std::vector<int>::const_iterator it = tab.begin(); it != end; ++it)
  std::cout << *it << std::endl;

// C++11
for (int i : tab)
  std::cout << i << std::endl;

0
Rejoignez-nous