24 votes

Comment dois-je appeler explicitement une exception jets de méthode en C++?

J'ai une classe simple:

class A {
 public:
  bool f(int* status = nullptr) noexcept {
    if (status) *status = 1;
    return true;
  }
  void f() {
    throw std::make_pair<int, bool>(1, true);
  }
};

int main() {
  A a;
  a.f(); // <- Ambiguity is here! I want to call 'void f()'
}

Je veux résoudre l'ambiguïté d'un appel de méthode en faveur de l'exception-lancement de la méthode par tous les moyens.

Le raisonnement derrière cette interface:

  • Pour avoir l' noexcept(true) et noexcept(false) interface,
  • Pour permettre éventuellement obtenir des informations supplémentaires par l'intermédiaire d'un pointeur dans l' noexcept(false) variante - alors que l' noexcept(true) variante sera toujours le pack de cette information à l'intérieur d'une exception.

Est-il possible? Suggestions pour une meilleure interface sont également les bienvenus.

43voto

bolov Points 4005

Ayant des fonctions avec ce genre de signatures est évidemment un mauvais design comme vous l'avez trouvé. Les solutions réelles pour avoir des noms différents pour eux ou perdre de la valeur par défaut de l'argument et ont été déjà présentés dans d'autres réponses.

Toutefois, si vous êtes coincé avec une interface vous ne pouvez pas changer ou juste pour le plaisir de celui-ci ici est de savoir comment vous pouvez appeler explicitement void f():

L'astuce est d'utiliser le pointeur de la fonction de casting pour résoudre l'ambiguïté:

a.f(); // <- ambiguity is here! I want to call 'void f()'

(a.*(static_cast<void (A::*)()>(&A::f)))(); // yep... that's the syntax... yeah...

Ok, donc ça fonctionne, mais ne jamais écrire du code comme ça!

Il y a des façons de le rendre plus lisible.

L'utilisation d'un pointeur:

// create a method pointer:
auto f_void = static_cast<void (A::*)()>(&A::f);

// the call is much much better, but still not as simple as `a.f()`
(a.*f_void)();

Créer un lambda ou une fonction libre

auto f_void = [] (A& a)
{
    auto f_void = static_cast<void (A::*)()>(&A::f);
    (a.*f_void)();
};

// or

void f_void(A& a)
{
    auto f_void = static_cast<void (A::*)()>(&A::f);
    (a.*f_void)();
};


f_void(a);

Je ne sais pas si c'est nécessaire de mieux. La syntaxe d'appel est certainement plus simple, mais elle peut être source de confusion, comme nous le sommes de commutation à partir d'une méthode de syntaxe d'appel d'une fonction libre syntaxe d'appel.

28voto

Les deux versions f ont des significations différentes.

Ils doivent avoir deux nom différents, comme:

  • f pour le jeter, parce que l'utiliser signifie que vous êtes confiant sur le succès et l'échec serait une exception dans le programme.
  • try_f() ou tryF() de l'erreur-retour en fonction de un, parce qu'en l'utilisant signifie que l'échec de l'appel est un résultat attendu.

Deux significations différentes devraient être reflétées dans la conception, avec deux noms différents.

16voto

davmac Points 4317

Parce qu'il semble fondamentalement évident pour moi, j'ai peut-être raté quelque chose ou risquent de ne pas comprendre votre question. Cependant, je pense que c'est exactement ce que vous voulez:

#include <utility>

class A {
 public:
  bool f(int* status) noexcept {
    if (status) *status = 1;
    return true;
  }
  void f() {
    throw std::make_pair<int, bool>(1, true);
  }
};

int main() {
  A a;
  a.f(); // <- now calls 'void f()'
  a.f(nullptr);  // calls 'bool f(int *)'
}

J'ai tout simplement supprimé l'argument par défaut de l' noexcept variante. Il est toujours possible d'appeler l' noexcept variante en passant nullptr comme argument, qui me semble un parfait moyen d'indiquer que vous voulez l'appeler variante particulière de la fonction - après tout, il va devoir être certains marqueur syntaxique indiquant que la variante que vous voulez l'appeler!

5voto

Christian Hackl Points 4763

Je suis d'accord avec d'autres suggestions d'utilisateurs de supprimer simplement l'argument par défaut.

Un argument fort en faveur d'une telle conception est qu'il serait en ligne avec la nouvelle C++17 système de fichiers de la bibliothèque, dont les fonctions offrent généralement des appelants le choix entre les exceptions et les erreurs des paramètres de référence.

Voir, par exemple, std::filesystem::file_size, ce qui a deux surcharges, l'un d'eux étant noexcept:

std::uintmax_t file_size( const std::filesystem::path& p );

std::uintmax_t file_size( const std::filesystem::path& p,
                          std::error_code& ec ) noexcept;

L'idée derrière cette conception (qui est à l'origine de Boost.Système de fichiers) est presque identique à la vôtre, à l'exception de l'argument par défaut. Le retirer et de vous le faire comme un tout nouveau composant de la bibliothèque standard, qui évidemment peut-être s'attendre à ne pas avoir complètement cassé la conception.

2voto

user9183966 Points 39

En C++14 c'est ambigu, car noexcept est pas partie de la signature de la fonction. Avec qui le dit...

Vous avez une très étrange de l'interface. Bien qu' f(int* status = nullptr) est étiqueté noexcept, parce qu'il a un jumeau qui ne jeter une exception, vous n'êtes pas vraiment en train de donner à l'appelant une logique exception de garantie. Il semble que vous simultanément souhaitez f de toujours réussir tout en lançant une exception si la condition n'est pas remplie (statut a une valeur valide, j'.e ne nullptr). Mais si f jette, dans quel état est l'objet? Vous voyez, votre code est très difficile de raisonner sur.

Je vous recommande de prendre un coup d'oeil à l' std::optional à la place. Il va de signal au lecteur ce que vous êtes en train d'essayer de faire.

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