web-dev-qa-db-fra.com

Qu'est-ce qui ne va pas avec "la vérification de l'auto-assignation" et qu'est-ce que cela signifie?

Dans le livre de Herb Sutter C++ exceptionnel (1999) , il a des mots dans la solution de l'article 10:

"Exception-non sécurisé" et "mauvaise conception" vont de pair. Si un élément de code n'est pas protégé contre les exceptions, c'est généralement correct et vous pouvez simplement le réparer. Mais si un élément de code ne peut pas être protégé contre les exceptions en raison de sa conception sous-jacente, c'est presque toujours le signe de sa mauvaise conception.

Exemple 1: une fonction avec deux responsabilités différentes est difficile à sécuriser les exceptions.

Exemple 2: un opérateur d'assignation de copie écrit de telle sorte qu'il doit vérifie si cette affectation est auto-assignée n'est probablement pas non plus fortement protégé contre les exceptions

Qu'entend-il par le terme "vérifier l'affectation personnelle"?

[ENQUÊTE]

Dave et AndreyT nous montrent exactement ce que "vérifier pour l'auto-affectation" signifie. C'est bon. Mais la question n'est pas terminée. Pourquoi "vérifier l'affectation personnelle" nuit-t-il à "la sécurité des exceptions" (selon Hurb Sutter)? Si l'appelant essaie de s'auto-assigner, cette "vérification" fonctionne comme si aucune assignation ne se produisait. Est-ce que ça fait vraiment mal?

[MÉMO 1] Au point 38 Identité d'objet plus tard dans le livre de Herb, il explique comment se faire soi-même.

25
Jimm Chen

La question la plus importante dans ce cas est de savoir ce que "écrit de telle manière qu'il doit vérifier l'auto-affectation" signifie.

Cela signifie qu'un opérateur d'affectation bien conçu ne devrait pas vérifier le {besoin} _ pour l'auto-affectation. L'affectation d'un objet à lui-même devrait fonctionner correctement (c'est-à-dire avoir pour effet final de "ne rien faire") sans effectuer de vérification explicite de l'auto-affectation.

Par exemple, si je voulais implémenter une classe tableau simpliste le long des lignes de

class array {
  ...
  int *data;
  size_t n;
};

et est venu avec la mise en œuvre suivante de l'opérateur d'affectation

array &array::operator =(const array &rhs) 
{
  delete[] data;

  n = rhs.n;
  data = new int[n];
  std::copy_n(rhs.data, n, data);

  return *this;
}

cette implémentation serait considérée comme "mauvaise" puisqu'elle échoue évidemment en cas d'auto-affectation. 

Pour le "réparer", on peut soit ajouter un contrôle explicite d’auto-affectation

array &array::operator =(const array &rhs) 
{
  if (&rhs != this) 
  {
    delete[] data;

    n = rhs.n;
    data = new int[n];
    std::copy_n(rhs.data, n, data);
  }

  return *this;
}

ou suivre une approche "check-less"

array &array::operator =(const array &rhs) 
{
  size_t new_n = rhs.n;
  int *new_data = new int[new_n];
  std::copy_n(rhs.data, new_n, new_data);

  delete[] data;

  n = new_n;
  data = new_data;

  return *this;
}

Cette dernière approche est meilleure dans le sens où elle fonctionne correctement dans les situations d’auto-affectation sans faire une vérification de {explicite}. (Cette mise en œuvre est encore loin d'être parfaite du point de vue de la sécurité des exceptions, elle est là pour illustrer la différence entre les approches "vérifiée" et "sans vérification" pour gérer l'auto-affectation). L'implémentation ultérieure sans vérification peut être écrite de manière plus élégante grâce au célèbre langage de copie-échange.

Cela ne signifie pas que vous devriez éviter les contrôles explicites pour l'auto-affectation. Une telle vérification a du sens du point de vue de la {performance}: il ne sert à rien de mener une longue séquence d'opérations simplement pour finir par "ne rien faire". Mais dans un opérateur d'affectation bien conçu, de telles vérifications ne devraient pas être nécessaires du point de vue de la {correction} _. 

33
AnT

De c ++ core guide

Foo& Foo::operator=(const Foo& a)   // OK, but there is a cost
{
    if (this == &a) return *this;
    s = a.s;
    i = a.i;
    return *this;
}

Ceci est évidemment sûr et apparemment efficace. Cependant, si nous faisons une auto-affectation par million d’affectations ? Cela représente environ un million de tests redondants (mais comme la réponse est essentiellement la même, le prédicteur de branche de l'ordinateur devinera exact à chaque fois). Considérer:

Foo& Foo::operator=(const Foo& a)   // simpler, and probably much better
{
    s = a.s;
    i = a.i;
    return *this;
}

Remarque: Le code ci-dessus s'applique uniquement aux classes sans pointeur, les classes avec pointeur pointant vers la mémoire dynamique. Veuillez vous reporter à la réponse de Ant.

3
camino
MyClass& MyClass::operator=(const MyClass& other)  // copy assignment operator
{
    if(this != &other) // <-- self assignment check
    {
        // copy some stuff
    }

    return *this;
}

Assigner un objet à lui-même est une erreur, mais cela ne devrait pas logiquement avoir pour conséquence de changer votre instance de classe. Si vous parvenez à concevoir une classe où l'attribution à elle-même la modifie, elle est mal conçue.

2
David

La raison générale pour vérifier l'auto-affectation est que vous détruisez vos propres données avant de les copier dans la nouvelle. Cette structure d'opérateur d'attribution n'est pas non plus fortement protégée contre les exceptions. 

En tant qu'additif, il a été déterminé que l'auto-affectation ne profite pas du tout aux performances, car la comparaison doit être exécutée à chaque fois, mais l'auto-affectation est extrêmement rare et, le cas échéant, il s'agit d'une erreur logique dans votre programme (vraiment) . Cela signifie que tout au long du programme, ce n’est qu’un gaspillage de cycles.

0
Puppy