Je suis en train de crée la classe ZFraction demande dans le cours de C++ d'Openclassrooms et en faisant quelques tests j'ai remarque que la division par 0 faisais planter mon programme... Du coup j' était parti pour faire une gestion d’erreurs a coup de true/false comme en C cependant le constructeur de ma Fraction ne "return" seulement une Fraction par definition... Donc si je veux arrêter mon programme et dire a mon utilisateur que sa fraction ne vas pas... soit c'est exit() mais c'est pas propre soit je sais pas... Comment faut - il gérer ce cas?
Je vous pose mon code ici bas:
Fraction.h
class Fraction
{
public:
Fraction(int x = 0, int y = 1);
void show() const;
void simplify();
int getNumerator() const;
int getDenominator() const;
private:
int mX{0};
int mY{1};
};
Fraction.cpp
Fraction::Fraction(int x, int y) : mX{x}, mY{y}
{
int pgcd = findPGCD(mX, mY);
if (pgcd != 0) {
mX /= pgcd;
mY /= pgcd;
}
if ((mX < 0 && mY < 0) || (mX > 0 && mY < 0)) {
mX *= -1;
mY *= -1;
}
}
void Fraction::show() const
{
if (mX == 0)
std::cout << "0" << std::endl;
else if (mY == 1)
std::cout << mX << std::endl;
else
std::cout << mX << "/" << mY << std::endl;
}
int findPGCD(int a, int b)
{
if (a == b)
return (a);
return (a % b == 0 ? b : findPGCD(b, a % b));
}
Merci d'avance !
PS: Si vous avez des commentaires sur la qualite du code, je prend!
PS: Je sais deja que le cours c++ d'OC est mauvais
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
Ta construction n'est effective qu'après avoir terminé l'exécution du bloc du constructeur. Donc simplement :
Q::Q(int num, unsigned den) :
m_num{ num },
m_den{ den }
{
assert(den != 0) ;
}
Si ton dénominateur est 0 à l'entrée, ça pètera l'assertion et le programme crashera purement et simplement. Interdisant ainsi au développeur de mettre 0 comme dénominateur.
class Rationnel {
explicit Rationnel(int n = 0, int d = 1)
: num(n), den(d)
{
assert(d >= 0);
reduit();
}
private:
void reduit();
int num;
int den;
};
void unefonction___ailleurs()
{
int n, d;
if (! (std::cin >> n >> d)) {
throw std::runtime_error("Impossible de lire deux entiers dans le flux");
// Pour bien fair,e il faudrait tester std::cin.eof() pour vérifier si l flux a été fermé -- cf FAQ: valider les saisies
}
if (d < 0) {
throw std::runtime_error("Un nombre positif est attendu pour un dénominateur");
}
if (d == 0) {
throw std::runtime_error("Un nombre non null est attendu pour un dénominateur");
}
// Et là, on a le rationel
const Rationnel r{n, d};
}
Ca serait peut être mieux de lancer une exception spécifique.
class ExceptionConstructionRationnel
: public std::runtime_error
{
public:
ExceptionConstructionRationnel()
: std::runtime_error("dénominateur nul")
{
}
};
class Rationnel {
int num;
int den;
public:
Rationnel(int n = 0, int d = 1)
: num(n), den(d)
{
if (d == 0) {
throw ExceptionConstructionRationnel();
}
}
};
- Edité par michelbillaud 22 octobre 2018 à 11:21:44
Oui, d'une certaine manière, ce serait en effet mieux d'avoir une exception spécifique, mais non, il ne faut pas lancer une exception au niveau du constructeur de la classe, car le fait que le dénominateur doit être non nul, c'est aussi une pré condition, que le développeur doit s'assurer de respecter...
Au niveau du constructeur, nous devrions donc utiliser une assertion, car un dénominateur nul représentera toujours une erreur de logique.
Au niveau de la fonction qui extrait les valeurs d'un flux, nous devrions utiliser une exception, car une valeur nulle lors de l'extraction du dénominateur est une situation "dont on espère qu'elle ne se produira jamais" sans pour autant pouvoir garantir que ce sera le cas
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
mais personne n'oblige le programmeur à rattraper les runtime exceptions, ce qui lui demande un travail _explicite_. Et on peut supposer que si il le fait, c'est qu'il a des raisons qui lui appartiennent.
Alors que les assertions désactivés par accident, ça arrive (dans les options de compilation).
#define NDEBUG
- Edité par michelbillaud 22 octobre 2018 à 11:57:36
mais personne n'oblige le programmeur à rattraper les runtime exceptions, ce qui lui demande un travail _explicite_. Et on peut supposer que si il le fait, c'est qu'il a des raisons qui lui appartiennent.
Alors que les assertions désactivés par accident, ça arrive (dans les options de compilation).
#define NDEBUG
- Edité par michelbillaud il y a moins de 30s
Nous sommes bien d'accord, tout comme nous sommes bien d'accord que, selon la manière dont le flux est rempli, nous ne pourrons rien faire si le dénominateur est nul.
Mais, a priori, tout bon développeur a au moins conscience du fait que sa logique doit être sans faille (elle peut ne pas être "aussi efficace que possible", elle doit au moins être ... juste), et qu'il y a donc une série de tests à faire :
Des tests unitaire (dont certains peuvent clairement "faire péter" les assertions)
des "tests du singe" qui permettront de s'assurer que l'utilisateur distrait ne fera pas "tout pêter"
Le simple fait d'avoir un test qui devrait faire péter l'assertion, mais qui ne le fait pas devrait indiquer ce problème de configuration
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Si on met l'exception dans le constructeur, on est dans le cas que j'ai décris dans mon premier billet de blog sur la PpC: OK, on ne plante pas, OK les inputs externes sont incidemment testées, mais on ne remonte pas de messages d'erreur intelligibles, et on paie tout le temps des tests dont on peut se passer une fois les inputs validées.
Je suis de moins en moins persuadé qu'il est une bonne chose de lancer des exceptions depuis des constructeurs. Pas qu'il ne faille pas en lancer, mais que les tests devraient être faits en amont, là où l'on sait ce que l'on essaie de faire à partir de quoi.
Quitte à forcer l'utilisateur à procéder correctement, on peut jouer avec
// TODO: ajouter constexpr, noexcept, les conversions implicites avec strictly_positive<U>, etc
template <typename T>
struct striclty_positive {
explicit striclty_positive(int i) n:(i)
{ assert(i > 0); }
explicit operator int() const { return n };
private:
int n;
};
class Rational
{
public:
Rational(int n, strictly_positive<int> d);
...
};
@lmghs : à titre de curiosité pour progresser (je sais que ça fait beaucoup de questions, mais ça m'intéresse de comprendre maintenant que j'ai fini le cours de Mateo21, car ces points ne sont pas dans son cours et avec la doc je n'ai pas compris) :
1) à quoi sert explicit dans cet exemple (tu l'as utilisé deux fois : une fois avec le constructeur et l'autre avec ton opérateur) ?
2) que signifie la notation : operator int() ?
3) qund tu parles de TODO ajouter : constexpr, et noexcept, peux-tu m'expliquer à quoi servent ces deux mots-clés ici ?
Merci
- Edité par pseudo-simple 22 octobre 2018 à 13:44:27
2- opérateur de conversion, explicite (c++11) -- en gros, on converti dans l'autre sens.
3- Prends-ton temps, tu n'y es pas encore. Le premier permet d'exiger du compilateur la garantie que des expressions seront évaluées à la compilation. Le second garantit qu'une fonction ne lance pas d'exception -- c'est plus compliqué en vrai. Comprends et vis d'abord le RAII.
Quand je vois ce lien sur explicit, je commence à me demander si dans le constructeur d'une classe, il ne faudrait pas systématiquement utiliser explicit ?
- Edité par pseudo-simple 22 octobre 2018 à 14:36:34
Alors pour le coup, je suis pas sur de tout comprendre ni de quel méthode empreinte a la différence du C, il y a plusieurs moyens de faire cela? Si j'ai bien suivi il y a 3 méthodes:
- la méthode qui utilise assert, qui va servir a faire juste crasher le programme si la condition est valide:
assert(y != 0);
- la méthode qui utilise throw, qui va "jeter" une information du type erreur, qu'une autre fonction va rattraper:
Rationnel(int x = 0, int y = 1) : mNum(x), mDen(y)
{
if (d == 0) {
throw ExceptionConstructionRationnel();
}
}
- la magie noire (parce que j'ai rien compris) qui consisterai a créer un variable `int non nulle`préalablement:
template <typename T>
struct striclty_positive {
explicit striclty_positive(int i) n:(i)
{ assert(i > 0); }
explicit operator int() const { return n };
private:
int n;
};
class Rational
{
public:
Rational(int n, strictly_positive<int> d);
...
};
J'aimerai avoir plus de détail en particulier sur cette dernière méthode qui me semble très intrigante... Que je puisse choisir la "meilleur" méthode
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
@YES, man => FAQ de dvpz, beaucoup de choses y sont expliquées, ou alors le livre de Koala01.
Par défaut, tout constructeur à un argument devrait être explicite, sauf si tu veux explicitement supporter une conversion -- cas de figure qui n'a de sens que sur les classes à sémantique de valeur.
@Rymfire, lis les 3 billets sur la PpC sur mon blog. La magie noire y est aussi abordée au travers de `gsl::not_null`. Ici, en tout honnêteté, pour avoir tenté l'expérience (partagée sur le forum il y a 2 ans?), la notion de strictly_positive est très insuffisante: il faudrait aller plus loin, et c'est complexe à faire de façon ergonomique.
En l'état, elle permet toutefois de forcer l'utilisateur à prendre conscience qu'un nombre positif est attendu, et à le forcer à prendre acte (casting, ou vérification dynamique).
La dernière méthode n'a rien d'incroyablement particulier, elle déplace juste le contrôle de la valeur dans un type, sinon elle est équivalente à la première. Et c'est à mon sens celle qu'il faut préférer parce que c'est un contrôle dont on veut se débarrasser lorsque la validation du programme a été réalisée.
Mais sinon, il n'y a rien de fou :
template <typename T>
struct striclty_positive {
// un constructeur qui impose d'être demandé explicitement
explicit striclty_positive(int i) n:(i)
{
// si l'entier fourni n'est pas strictement positif on
// colle un assert dans la bouche de l'appelant.
assert(i > 0);
}
// on fournit un opérateur pour "convertir" simplement
// notre entier strictement positif en entier.
explicit operator int() const { return n };
private:
int n;
};
Et du coup si on reçoit cet entier en entrée, on a la garantie qu'il est positif strictement et que l'on peut le transformer en entier.
C'est l'inconvénient de poser des questions à des spécialistes, ils embrouillent tout avec des considérations subtiles et des mécanismes d'enfer qu'ils tiennent à montrer.
Pour en revenir au problème : on a un constructeur, pendant la construction il y a quelque chose qui foire, on s'en rend compte alors qu'est ce qu'on fait ?
Le cadre des fractions est un peu trop restreint, parce que
il n'y a qu'une seule raison pour laquelle ça peut merder (dénominateur incorrect)
on pouvait s'en rendre compte avant d'appeler le constructeur.
Mais bon, "yakavépa appeler le constructeur", c'est pas une réponse généralisable. Il est fréquent d'appeler un constructeur qui a besoin de faire, par exemple, une allocation dynamique pour un de ses membres, qui peut échouer pour des raisons X ou Y. Qu'est-ce qu'on fait ?
Evidemment, il y a des cas où on peut plier le rideau par exit() ou assert(). Dans les programmes qui servent à rien. Mais bon, en général il y a des trucs à faire pour fermer la boutique proprement, et la réponse générique, c'est une exception.
Et comme ça peut arriver dans la liste d'initialisation, y a même une syntaxe exprès.
Pour en revenir au problème : on a un constructeur, pendant la construction il y a quelque chose qui foire, on s'en rend compte alors qu'est ce qu'on fait ?
La réponse est simple : la logique est foireuse et doit être modifiée avant de passer en "production" / d'être envoyé à son prof.
Logique (potentiellement) foireuse == assertion. Point Barre. Cela se traduira forcément par un code proche de
Les exceptions, c'est pour les cas où une situation "à laquelle le développeur ne peut rien" risquerait -- justement -- de provoquer une erreur de logique. Par exemple, l'extraction de données à partir d'un flux:
std::isttream & operator >>(std::istream & ifs, Fraction & fr){
type tempenumerator;
type tempdenominator;
if(! ifs>>tempnumerator) { // oups... l'extraction foire
/* pas cool, parce que la suite foirera aussi
*/
throw CannotGetEnumerator();
}
if(! ifs>>tempdenominator) { // oups... l'extraction foire
/* pas cool, parce que la suite foirera aussi
*/
throw CannotGetDenominator();
}
if(tempdenominator == 0) { //l'extraction s'est bien passée
/* mais ca fera péter l'asseriton à l'étape suivante
* on ne peut donc pas laisser faire, mais on n'a
* aucun moyen d'y remédier
*/
throw NullDenominator();
}
/* Oufff!!! arrivés ici, nous sommes en théorie certains
* d'avoir des données cohérentes
*
* nous pouvons donc aller plus loin
*/
fr = Fraction(tempnumerator, tempdenominator);
return ifs;
}
michelbillaud a écrit:
Evidemment, il y a des cas où on peut plier le rideau par exit() ou assert(). Dans les programmes qui servent à rien. Mais bon, en général il y a des trucs à faire pour fermer la boutique proprement, et la réponse générique, c'est une exception.
Non!
La réponse générique se fait en deux temps :
ce qui est du ressort du développeur (de l'utilisateur) de la fonction se gère au travers d'une vérification des préconditions, et donc, au travers d'une assertion,
Ce qui n'est du ressort ni du développeur ni de l'utilisateur mais qui pourrait dégénérer en erreur de logique (et, par effet de rebond, faire péter une assertion) se gère au travers des exceptions.
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Pour moi une assertion ça s'utilise quand tu ne sais pas quoi mettre dans un catch.
Si tu peux rattraper le coup et insulter copieusement l'utilisateur en lui printant au passage une bonne cinquantaine de fois la raison pour laquelle tu viens de lui sauver le derrière, quoi de plus plaisant de le faire.
Evidemment, il y a des cas où on peut plier le rideau par exit() ou assert(). Dans les programmes qui servent à rien. Mais bon, en général il y a des trucs à faire pour fermer la boutique proprement, et la réponse générique, c'est une exception.
Non!
La réponse générique se fait en deux temps :
ce qui est du ressort du développeur (de l'utilisateur) de la fonction se gère au travers d'une vérification des préconditions, et donc, au travers d'une assertion,
Ce qui n'est du ressort ni du développeur ni de l'utilisateur mais qui pourrait dégénérer en erreur de logique (et, par effet de rebond, faire péter une assertion) se gère au travers des exceptions.
Sachant que précisément, un constructeur qui ne peut pas faire ce qu'on en attend ne le signale pas, ça entraine forcément une "erreur de logique" dans le reste de l'application, qui n'est pas au courant. Ou alors c'est que le constructeur ne faisait rien d'utile...
Donc on est d'accord.
Mais y a un age où il faut dire "non", on est tous passés par là :-)
Mais y a un age où il faut dire "non", on est tous passés par là :-)
Et j'imagine qu'il y a aussi un âge où on abandonne l'idée de vouloir éviter d'avoir des tests inutiles à l'exécution. J'imagine qu'on finit tous par passer par là aussi.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C