32 votes

Pourquoi std :: unique_ptr :: reset () est-il toujours noexcept?

Une récente question (et surtout de ma réponse) m'a fait me demander:

En C++11 (et plus récentes normes), les destructeurs sont toujours implicitement noexcept, sauf indication contraire (c'est à dire noexcept(false)). Dans ce cas, ces destructeurs peuvent légalement se lancer des exceptions. (Notez que c'est encore vous devez vraiment savoir ce que vous faites cegenre de situation!)

Cependant, tous les surcharges de std::unique_ptr<T>::reset() sont déclarés d'être toujours noexcept (voir cppreference), même si le destructeur s' T n'est pas le cas, résultant en fin de programme si un destructeur, déclenche une exception lors de l' reset(). Des choses semblables s'appliquent std::shared_ptr<T>::reset().

Pourquoi est - reset() toujours noexcept, et pas conditionnellement noexcept?

Il devrait être possible de déclarer noexcept(noexcept(std::declval<T>().~T())) ce qui le rend noexcept exactement si le destructeur de l' T est noexcept. Ai-je raté quelque chose ici, ou est-ce un oubli dans la norme (car ce n'est certes un très académique de la situation)?

26voto

Niall Points 6133

Les exigences de l'appel à la fonction d'objet Deleter sont précis sur ce que figurant dans les exigences de l' std::unique_ptr<T>::reset() membre.

À partir de [unique.ptr.unique.modificateurs]/3, circa N4660 §23.11.1.2.5/3;

unique_ptr modificateurs

void reset(pointer p = pointer()) noexcept;

Nécessite: L'expression get_deleter()(get()) sont bien formés, est bien définie comportement, et de ne pas lancer des exceptions.

En général, le type aurait besoin d'être destructibles. Et comme par le cppreference sur le C++ concept Destructibles, les listes standard présente sous la table dans [utilitaire.arg.exigences]/2, §20.5.3.1 (les italiques sont de moi);

Destructible exigences

u.~T() De toutes les ressources détenues par u sont récupérés, pas d'exception est propagée.

Notez également la bibliothèque générale des exigences pour les fonctions de remplacement; [res.sur.fonctions]/2.

6voto

VTT Points 27056

std::unique_ptr::reset n'invoque pas le destructeur directement, au lieu de cela, il invoque operator () de la deleter paramètre du modèle (qui par défaut est std::default_delete<T>). Cet opérateur est tenu de ne pas lancer des exceptions, comme spécifié dans

23.11.1.2.5 unique_ptr modificateurs [unique.ptr.unique.modificateurs]

void reset(pointer p = pointer()) noexcept;

Nécessite: L'expression get_deleter()(get()) doit être bien formé, doit avoir >bien défini comportement, et ne doit pas jeter des exceptions.

Noter que ne doit pas jeter n'est pas le même que noexcept si. operator () de la default_delete n'est pas déclaré en tant que noexcept même si elle n'invoque delete (opérateur exécute delete déclaration). Il semble donc plutôt un point faible dans la norme. reset devrait être conditionnelle noexcept:

noexcept(noexcept(::std::declval<D>()(::std::declval<T*>())))

ou operator () de la deleter doit être noexcept afin de donner une plus solide garantie.

4voto

martiert Points 471

Sans avoir été dans les discussions du comité de normalisation, ma première pensée est que c'est un cas où le comité de normalisation a décidé que la douleur de la jeter dans le destructeur, qui est généralement considéré comme un comportement indéterminé en raison de la destruction de la pile de la mémoire lors du dénouement de la pile, n'était pas la peine.

Pour l' unique_ptr , en particulier, d'envisager ce qui pourrait arriver si un objet tenu par un unique_ptr jette dans le destructeur:

  1. L' unique_ptr::reset() est appelé.
  2. L'objet à l'intérieur est détruit
  3. Le destructeur jette
  4. La pile commence déroulement
  5. L' unique_ptr est hors de portée
  6. Goto 2

Il y a des façons d'éviter ce problème. On est le pointeur à l'intérieur de l' unique_ptr d'un nullptr avant de le supprimer, ce qui entraînerait une fuite de mémoire, ou de définir ce qui doit arriver si un destructeur, déclenche une exception dans le cas général.

0voto

OLL Points 555

Peut-être que ce serait plus facile à expliquer avec un exemple. Si nous supposons que reset n'a pas toujours noexcept, puis on a pu écrire du code comme cela causerait des problèmes:

class Foobar {
public:
  ~Foobar()
  {
    // Toggle between two different types of exceptions.
    static bool s = true;
    if(s) throw std::bad_exception();
    else  throw std::invalid_argument("s");
    s = !s;
  }
};

int doStuff() {
  Foobar* a = new Foobar(); // wants to throw bad_exception.
  Foobar* b = new Foobar(); // wants to throw invalid_argument.
  std::unique_ptr<Foobar> p;
  p.reset(a);
  p.reset(b);
}

Que faisons-nous lorsque p.reset(b) est appelé?

Nous voulons éviter les fuites de mémoire, donc, p des besoins de revendiquer la propriété de l' b , de sorte qu'il peut détruire l'instance, mais elle a aussi besoin de détruire a qui veut lancer une exception. Alors, comment et nous détruire à la fois a et b?

Aussi, les exceptions qui devrait doStuff() jeter? bad_exception ou invalid_argument?

Forçant reset d'être toujours noexcept évite ces problèmes. Mais ce genre de code serait rejetée au moment de la compilation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X