5
votes

Est-ce que make_unique dans la liste d'initialisation dans le constructeur de copie est un bon but de ne pas utiliser le spécificateur noexcept?

J'ai un obstacle avec le spécificateur noexcept à côté de mon constructeur de copie.

#include <memory>
#include <vector>

class Foo final {
 public:
  Foo() noexcept = default;
  Foo(const Foo& oth) : impl_(std::make_unique<Foo::Impl>()) {} // <---
  ~Foo() noexcept = default;

private:
  class Impl;
  std::unique_ptr<Impl> impl_;
};

class Foo::Impl {
  ...
 private:
  std::vector<int> some_data;
}

Je ne sais pas si je dois mettre noexcept à côté du constructeur de copie pendant que je suis là est std :: make_unique qui peut lancer bad_alloc.

Toute aide sera appréciée!


0 commentaires

5 Réponses :


1
votes

Je pense que cela dépend vraiment du contexte. Si vous pouvez raisonnablement gérer cette exception (et vous attendre à ce qu'elle soit levée), vous ne devriez pas la marquer comme noexcept . En revanche, s'il n'y a aucun moyen pour votre programme de récupérer de l'exception, vous pouvez aussi bien le marquer comme noexcept .
Je dirais que le deuxième cas est vrai lorsque l'objet à allouer est plutôt petit (vous ne pourrez pas faire beaucoup de gestion des exceptions si vous ne pouvez pas allouer un seul char ). Le premier devrait se produire pour les objets très volumineux (vous pouvez alors reporter l'allocation par exemple). Cela étant dit, si vous copiez un objet énorme ... Pourquoi ne pas le déplacer à la place?


0 commentaires

1
votes

Réponse simple : ne le déclarez pas, sauf si vous savez qu'il peut lancer et que vous n'avez pas vraiment de bonne raison de le faire (par exemple, vous voulez que votre application appelle std :: se terminer si une copie ne peut être faite).

Demandez-vous quel serait le gain. Le compilateur peut-il optimiser quoi que ce soit quand ce n'est pas le cas? Je ne vois pas beaucoup de cas où ce serait le cas (le cas le plus courant où je vois des optimisations est pour un déplacement, car alors les conteneurs de la bibliothèque std peuvent l'utiliser - ils vérifient si une opération de déplacement est no, sauf pour pouvoir garder leurs garanties ). L'autre endroit où vous pouvez l'utiliser est lorsque vous souhaitez documenter qu'une fonction ne peut pas lancer. Ce n'est évidemment pas le cas ici.

D'un autre côté, vous ne pourrez plus récupérer d'une éventuelle exception et votre programme se terminera.

Donc en été, vous ne gagnez rien, mais vous risquez de perdre quelque chose.

Consultez également les directives de base:

E.12: Utiliser noexcept lors de la sortie d'une fonction parce qu'un lancer est impossible ou inacceptable

Le titre de cette règle peut être un peu déroutant. Il dit que vous devez déclarer une fonction comme noexcept, si

  • il ne jette pas ou
  • vous ne vous souciez pas en cas d'exception. Vous êtes prêt à planter le programme car vous ne pouvez pas gérer une exception telle que std :: bad_alloc en raison d'un épuisement de la mémoire.

Ce n'est pas une bonne idée de lever une exception si vous êtes le propriétaire direct d'un objet.

À propos, les fonctions membres spéciales suivantes sont implicitement noexcept:

  • Constructeur par défaut
  • Destructeur (pour autant que je sache même si vous le lancez explicitement)
  • Déplacer et copier le constructeur
  • Déplacer et copier l'opérateur d'affectation

2 commentaires

A propos du destructeur: En pratique, les destructeurs implicites sont noexcept sauf si la classe est "empoisonnée" par une base ou un membre dont le destructeur est noexcept (false)


@ t.niese Très bien, je pense que c'est conforme à ce que j'ai écrit. Merci de l'avoir éclairci



1
votes

<₹ Quand utiliser noexcept

"bonnes pratiques"

Devoir se demander si je dois ou non ajouter noexcept après chaque déclaration de fonction réduirait considérablement la productivité du programmeur (et franchement, serait pénible).

Eh bien, utilisez-le quand il est évident que la fonction ne sera jamais lancée.

Quand puis-je m'attendre de manière réaliste à observer une amélioration des performances après avoir utilisé noexcept ? [...] Personnellement, je me soucie de noexcept à cause de la liberté accrue offerte au compilateur pour appliquer en toute sécurité certains types d'optimisations.

Il semble que les gains d'optimisation les plus importants proviennent des optimisations de l'utilisateur, et non de celles du compilateur en raison de la possibilité de vérifier noexcept et de le surcharger. La plupart des compilateurs suivent une méthode de gestion des exceptions sans pénalité si vous ne lancez pas, donc je doute que cela changerait beaucoup (ou quoi que ce soit) au niveau du code machine de votre code, bien que peut-être réduire la taille binaire en supprimant la manipulation code.

L'utilisation de noexcept dans le big 4 (constructeurs, affectation, pas destructeurs car ils sont déjà noexcept ) entraînera probablement les meilleures améliorations car noexcept les contrôles sont «communs» dans le code de modèle, comme dans les conteneurs std. Par exemple, std :: vector n'utilisera pas le déplacement de votre classe à moins qu'il ne soit marqué noexcept (ou que le compilateur puisse le déduire autrement).


0 commentaires

4
votes

Les instructions de codage cpp sont assez claires à ce sujet dans E .12: Utiliser noexcept lors de la sortie d'une fonction parce qu'un lancer est impossible ou inacceptable

Vous pouvez donc utiliser noexcept même si l'appel de cette fonction / ctor peut entraîner une exception , si cette exception devait - à votre avis - entraîner un état non gérable de votre application.

Exemple tiré des directives:

vector<double> munge(const vector<double>& v) noexcept
{
    vector<double> v2(v.size());
    // ... do something ...
}

Le noexcept indique ici que je ne suis pas disposé ou capable de gérer la situation où je ne peux pas construire le vecteur local. Autrement dit, je considère l'épuisement de la mémoire comme une grave erreur de conception (comparable aux pannes matérielles), de sorte que je suis prêt à planter le programme si cela se produit.

Donc, si une construction échouée de Foo peut être gérée en utilisant un bloc try-catch sans problèmes sérieux. Alors vous n'utiliserez pas de noexcept là-bas.


0 commentaires

3
votes

Réflexion sur d'autres réponses: je n'utiliserais pas noexcept si une fonction est potentiellement lancée, même si vous ne vous souciez pas de savoir si votre programme se terminera si c'est le cas. Parce que ce sera le cas, si une fonction déclarée comme noexcept lance. Déclarer que votre fonction noexcept contient des informations sémantiques pour les utilisateurs de votre classe, ils peuvent dépendre de ces informations, ce qui est essentiellement faux dans votre cas.

EDIT: Je vous recommande de lire l'article 14 du C ++ moderne efficace de Scott Meyers, il décrit bien les avantages de l'utilisation de noexcept et quand l'utiliser.


0 commentaires