339 votes

Lancer des exceptions de constructeurs

Je vais avoir une discussion avec un collègue à propos de lever des exceptions de constructeurs, et de la pensée, je tiens quelques commentaires.

Est-il ok pour lancer des exceptions de constructeurs, de former une conception de point de vue?

Disons que je suis en l'enveloppant d'un mutex posix dans une classe, il ressemblerait à quelque chose comme ça.

class Mutex {
public:
  Mutex() {
    if(pthread_mutex_init(&mutex_,0)!=0) {
      throw MutexInitException();
    }
  }

  ~Mutex() {
    pthread_mutex_destroy(&mutex_);
  }

  void lock() {
    if(pthread_mutex_lock(&mutex_)!=0) {
      throw MutexLockException();
    }
  }

  void unlock() {
    if(pthread_mutex_unlock(&mutex_)!=0) {
      throw MutexUnlockException();
    }
  }

private:
  pthread_mutex_t mutex_;
};

Ma question est, est-ce la norme de moyen de le faire? parce que si le pthread mutex_init d'échec de l'appel de l'objet mutex est inutilisable donc lancer une exception s'assure que le mutex n'est pas créée.

Devrais-je plutôt créer un membre de la fonction init de la classe Mutex et appel pthread mutex_init dans qui permettrait le retour d'un booléen basé sur pthread mutex_init de retour? De cette façon, je n'ai pas à utiliser les exceptions pour un si faible niveau d'objet.

303voto

Naveen Points 37095

Oui, la levée d'une exception de l'échec de l'constructeur est la façon habituelle de faire. Lisez cette FAQ à propos de la Manipulation d'un constructeur qui échoue pour plus d'informations. Avoir une méthode init (), mais tout le monde qui crée l'objet de mutex est de se rappeler que la fonction init() doit être appelée. Je sens qu'il va à l'encontre de la RAII principe.

122voto

Ferruccio Points 51508

Si vous lancez une exception d'un constructeur, gardez à l'esprit que vous devez utiliser la syntaxe try / catch de la fonction si vous avez besoin d'attraper cette exception dans une liste d'initialisation de constructeur.

par exemple

 func::func() : foo()
{
    try {...}
    catch (...) // will NOT catch exceptions thrown from foo constructor
    { ... }
}
 

contre.

 func::func()
    try : foo() {...}
    catch (...) // will catch exceptions thrown from foo constructor
    { ... }
 

17voto

Richard Corden Points 12292

Il est OK de lancer depuis votre constructeur, mais vous devez vous assurer que votre objet est construit après le démarrage de principal et avant qu'il ne se termine:

 class A
{
public:
  A () {
    throw int ();
  }
};

A a;     // Implementation defined behaviour if exception is thrown (15.3/13)

int main ()
{
  try
  {
    // Exception for 'a' not caught here.
  }
  catch (int)
  {
  }
}
 

4voto

sysexpand Points 1038

Si votre projet s'appuie habituellement sur les exceptions pour les distinguer de mauvaises données à partir des données de bonne qualité, puis lancer une exception à partir du constructeur est de meilleure solution que de ne pas le jeter. Si l'exception n'est pas levé, puis l'objet est initialisé dans un état zombie. Un tel objet doit exposer un drapeau qui indique si l'objet est correcte ou non. Quelque chose comme ceci:

class Scaler
{
    public:
        Scaler(double factor)
        {
            if (factor == 0)
            {
                _state = 0;
            }
            else
            {
                _state = 1;
                _factor = factor;
            }
        }

        double ScaleMe(double value)
        {
            if (!_state)
                throw "Invalid object state.";
            return value / _factor;
        }

        int IsValid()
        {
            return _status;
        }

    private:
        double _factor;
        int _state;

}

Le problème avec cette approche est sur le côté de l'appelant. Chaque utilisateur de la classe aurait faire un si avant d'utiliser l'objet. Ceci est un appel pour les bugs - il n'y a rien de plus simple que d'oublier de tester une condition avant de continuer.

En cas de lancement d'une exception à partir du constructeur, d'une entité qui construit l'objet est supposé prendre soin de problèmes immédiatement. Objet les consommateurs vers le bas le flux sont libres de supposer que l'objet est 100% opérationnel à partir du simple fait qu'ils ont obtenu.

Cette discussion peut continuer dans de nombreuses directions.

Par exemple, en utilisant les exceptions comme une question de validation est une mauvaise pratique. Une façon de le faire est d'Essayer un modèle en collaboration avec l'usine de la classe. Si vous utilisez déjà des usines, puis écrire deux méthodes:

class ScalerFactory
{
    public:
        Scaler CreateScaler(double factor) { ... }
        int TryCreateScaler(double factor, Scaler **scaler) { ... };
}

Avec cette solution, vous pouvez obtenir le statut de drapeau en place, en tant que valeur de retour de la méthode de fabrique, sans jamais entrer dans le constructeur par de mauvaises données.

La deuxième chose est que si vous êtes couvrant le code avec des tests automatisés. Dans ce cas, chaque morceau de code qui utilise l'objet qui ne jette pas les exceptions devront être couverts avec un test supplémentaire - s'agit-il correctement lorsque la méthode IsValid() renvoie la valeur false. C'est ce qui explique assez bien que l'initialisation des objets en état zombie est une mauvaise idée.

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