Exception d’exception de l’initialiseur du constructeur

Quel est le meilleur moyen de lancer une exception de l’initialiseur du constructeur?

Par exemple:

class C { T0 t0; // can be either valid or invalid, but does not throw directly T1 t1; // heavy object, do not construct if t0 is invalid, by throwing before C(int n) : t0(n), // throw exception if t0(n) is not valid t1() {} }; 

Je pensais peut-être faire du wrapper, par exemple t0(throw_if_invalid(n)) .

Quelle est la pratique pour traiter de tels cas?

Je pense qu’il y a plusieurs façons de s’y prendre. D’après ce que j’ai compris, n ne peut prendre qu’une plage de nombres spécifique. Pour cela, vous pourriez empêcher le constructeur d’être même lancé:

 template  class ranged_type_c { public: typedef T value_type; ranged_type_c(const value_type& pX) : mX(pX) { check_value(); } const value_type& get(void) const { return mX; } operator const value_type&(void) const { return get(); } // non-const overloads would probably require a proxy // of some sort, to ensure values remain valid private: void check_value(void) { if (mX < Min || mX > Max) throw std::range_error("ranged value out of range"); } value_type mX; }; 

Pourrait être plus détaillé, mais c’est l’idée. Maintenant, vous pouvez serrer la plage:

 struct foo_c { foo_c(ranged_value_c i) : x(i) {} int x; }; 

Si vous transmettez une valeur qui ne se situe pas entre 0 et 100, le résultat ci-dessus est renvoyé.


Au moment de l’exécution, je pense que votre idée initiale était la meilleure:

 template  const T& check_range(const T& pX, const T& pMin, const T& pMax) { if (pX < pMin || pX > pMax) throw std::range_error("ranged value out of range"); return pValue; } struct foo { foo(int i) : x(check_range(i, 0, 100)) {} int x; } 

Et c’est tout. Comme ci-dessus, mais 0 et 100 peuvent être remplacés par un appel à une fonction qui renvoie le minimum et le maximum valides.

Si vous finissez par utiliser un appel de fonction pour obtenir des plages valides (recommandé, pour limiter l’encombrement au minimum et améliorer l’organisation), j’appendais une surcharge:

 template  const T& check_range(const T& pX, const std::pair& pRange) { return check_range(pX, pRange.first, pRange.second); // unpack } 

Pour permettre des choses comme ça:

 std::pair get_range(void) { // replace with some calculation return std::make_pair(0, 100); } struct foo { foo(int i) : x(check_range(i, get_range())) {} int x; } 

Si je devais choisir, je choisirais les méthodes d’exécution même si la plage était à la compilation. Même avec une faible optimisation, le compilateur générera le même code. Il est beaucoup moins lourd et sans doute plus propre à lire que la version de classe.

Vous pouvez throw les expressions qui initialisent t0 ou t1 ou tout constructeur prenant au moins un argument.

 class C { T0 t0; // can be either valid or invalid, but does not throw directly T1 t1; // heavy object, do not construct if t0 is invalid, by throwing before C(int n) // try one of these alternatives: : t0( n_valid( n )? n : throw my_exc() ), // sanity pre-check OR t1( t0.check()? throw my_exc() : 0 ), // add dummy argument to t1::t1() OR t1( t0.check()? throw my_exc() : t1() ) // throw or invoke copy/move ctor {} }; 

Notez qu’une expression throw est de type void , ce qui le fait davantage ressembler à un opérateur qu’à une instruction. L’opérateur ?: un cas particulier pour empêcher ce void d’interférer avec sa déduction de type.

C’est un moyen de jeter de la liste d’initialisation

 C(int n) : t0(n > 0 ? n : throw std::runtime_error("barf")), t1() {} 

Vous dites “lancer une exception si t0 (n) n’est pas valide”. Pourquoi ne jetez-vous pas du constructeur de T0?

Un object est censé être valide après la construction.

Enroulez simplement la classe T0 dans une autre classe qui lance dans des cas comme celui-ci:

 class ThrowingT0 { T0 t0; public: explicit ThrowingT0(int n) : t0(n) { if (t0.SomeFailureMode()) throw std::runtime_error("WTF happened."); }; const T0& GetReference() const { return t0; }; T0& GetReference() { return t0; }; }; class C { ThrowingT0 t0; T1 t1; public: explicit C(int n) : t0(n), t1() { }; void SomeMemberFunctionUsingT0() { t0.GetReference().SomeMemberFunction(); }; };