Exceptions c ++ POO

2

I must create a program with the triangle class and then create a class with the following exceptions:

  • An error message is issued if one of the sides has a value of zero.
  • An error message is issued (at its own decision) if one of the sides of the triangle is negative.
  • An error message is issued if a triangle could not be built (violation of the triangular inequality).
  • Suppose there is a triangle resize (itn factor) {... .code ...} method; in the class triangle that allows us to scale a triangle object according to the introduced factor, ie: if the factor = -2 means that we are going to reduce the triangle 2 units (sides / 2), on the contrary if factor = 2, it means that let's increase the triangle 2 units (sides * 2). Control exceptions for when the triangle can no longer be reduced and for a possible division by zero (factor = 0).

This is the code that I have developed so far:

#pragma once
#ifndef Triangle6_h
#define Triangle6_h
#include <iostream>
#include <cmath>
using namespace std;

class Triangle {
protected:
    int lado1, lado2, lado3;
public:
    Triangle() {
    }
    Triangle(int lado1, int lado2, int lado3) {
        this->lado1 = lado1;
        this->lado2 = lado2;
        this->lado3 = lado3;
    }
    void setLado1(int l1) {
        lado1 = l1;
    }
    int getLado1() {
        return lado1;
    }
    void setLado2(int l2) {
        lado2 = l2;
    }
    int getLado2() {
        return lado2;
    }
    void setLado3(int l3) {
        lado3 = l3;
    }
    int getLado3() {
        return lado3;
    }
    double area() {
        double s, area;
        s = (lado1 + lado2 + lado3) / 2;
        area = sqrt(s*(s - lado1)*(s - lado2)*(s - lado3));
        return area;
    }
    int perimetro() {
        return (lado1 + lado2 + lado3);
    }
    Triangle resize(int x) {
        int l1 = lado1;
        int l2 = lado2;
        int l3 = lado3;

        switch (x) {
        case(2):
            lado1 = (l1 * x);
            lado2 = (l2 * x);
            lado3 = (l3 * x);
            cout << "Lado 1: " << lado1 << "\nLado 2: " << lado2 << "\nLado 3: " << lado3 << endl;
            break;
        case(-2):
            lado1 = (l1 / x);
            lado2 = (l2 / x);
            lado3 = (l3 / x);
            cout << "Lado 1: " << lado1 << "\nLado 2: " << lado2 << "\nLado 3: " << lado3 << endl;
            break;
        }

    }
};
#endif /*Triangle6_h*/


#pragma once
#ifndef myexception1_h
#define myexception1_h
#include <iostream>
#include <exception>
using namespace std;

class myexception : public exception
{
    virtual const char* what(int factor) const throw(){
        switch (factor) {
        case(0):
            return "Error: Uno de los lados tiene valor 0.";
            break;
        case(1):
            return "Error: Uno de los lados tiene valor negativo.";
            break;
        case(2):
            return "Error: Violacion de la desigualdad triangular.";
            break;
        case(3):
            return "Error: No se puede reducir mas el triangulo.";
            break;
        case(4):
            return "Error: Division por 0.";
        }
    }
};
#endif /*myexception1_h*/

#include <iostream>
#include "Triangle6.h"
#include "myexception1.h"
using namespace std;

int main() {
    Triangle Tri(2,2,2);
    myexception myex;
    int a, b, c;

    cout << "Introduzca los tres lados de un triangulo: ";
    cin >> a >> b >> c;
    bool flag = true;
    do {
        try {
            if (a == 0) {
                throw myex;
            }
            else if (b == 0) {
                throw myex;
            }
            else if (c == 0) {
                throw myex;
            }
            else {
                Tri.setLado1(a);
                Tri.setLado2(b);
                Tri.setLado3(c);
            }
        }
        catch (exception& e1) {
            cout << e1.what(0) << "Uno de los lados es 0." << '\n';
            flag = false;
        }

        try {
            if (a < 0) {
                throw myex;
            }
            else if (b < 0) {
                throw myex;
            }
            else if (c < 0) {
                throw myex;
            }
            else {
                Tri.setLado1(a);
                Tri.setLado2(b);
                Tri.setLado3(c);
            }
        }
        catch (exception& e2) {
            cout << e2.what(1) << "Uno de los lados es negativo." << '\n';
            flag = false;
        }

        try {
            if (a > b + c) {
                throw myex;
            }
            else if (b > a + c) {
                throw myex;
            }
            else if (c > a + b) {
                throw myex;
            }
            else {
                Tri.setLado1(a);
                Tri.setLado2(b);
                Tri.setLado3(c);
            }
        }
        catch (exception& e3) {
            cout << e3.what(2) << "La desigualdad triangular no se cumple." << '\n';
            flag = false;
        }
    } while (flag == true);
    return 0;
}

As far as I know what () can not be passed parameters, but I can not think of another way to print the different exceptions with switch.

    
asked by davidllerenav 07.11.2018 в 15:33
source

2 answers

3

Do not lie with the guards

The guards are those precompiler directives that are put in the headers so that they are only loaded once. If the guards are not put the compiler will find duplicate declarations and implementations and will begin to show errors as if there were no tomorrow.

The standard form to define a guard is:

#ifndef SIMBOLO_UNICO
#define SIMBOLO_UNICO

// contenido de la cabecera

#endif

There is an alternative form that is quite widespread but may not be supported by some compilers:

#pragma once

In your case you are using both guards:

#pragma once
#ifndef Triangle6_h
#define Triangle6_h

And that is not necessary. My advice is to leave only one of the two. Quite complex can become a program in C ++ to fill the code of elements that do not add value.

Visibility and polymorphism

Class myexception does not define explicit visibility, so its members will use default visibility.

class use private as visibility by default, while struct use public . This is, in fact, the only difference between struct and class .

Since you used class , the what method is private and can not be used directly:

myexception exc;
std::cout << exc.what(2); // Error: what es privado

Now, std::exception defines what as virtual . This means that any child class can overwrite its behavior ... but we have a problem at this point and that is that the function what of std::exception has no parameters , while that of myexception yes This means that the function of myexception is different to the one declared in std::exception .

At this point it should be noted that although the base class std::exception returns a const char * , we internally can use the types of data that best suit our needs. We just have to take care that what returns a message in const char* format.

I personally, for these cases I prefer to use std::string for several reasons:

  • The code is cleaner.
  • Working with std::string is simpler than using const char* .
  • The code is more secure and less problematic.

Now, how do we convert std::string in const char* ? To answer this question we have to know the function c_str() . This function returns a pointer of type char* to the internal memory of std::string :

class myexception : public std::exception
{
  std::string msg;

public:
    myexception(std::string msg)
      : msg(msg)
    { }

    char const* what() const noexcept override
    //                       ~~~~~~~~ ~~~~~~~~ C++11
    {
      return msg.c_str();
    }
};

As you can see, the exception receives the message in the constructor and the reason is simple to understand. At what point do you have all the information about a problem, at the time it occurs or at any other time? Well, that's it, the information about what happened has it in the moment in which a problem is generated and that's where you generate your exception with great detail ... then you throw the exception and who receives it (wherever the receiver), you can analyze those details to know what has happened.

If the receiver already had all the information then the exception would not be necessary ... if I already know that an error has occurred I do not need anybody to notify me.

Everything in its place

Your function main , as far as exceptions are concerned, is absolutely responsible for everything. Launches exceptions and manages them. That design is not consistent. Who should know if the entered data is valid is the class Triangle , so it should be the one that launched, when appropriate, the exceptions.

Because of this it is recommended to carry out certain changes in the Triangle interface:

class Triangle {
private:
    int lados[3];

public:
    Triangle()
    {
    }

    void setLados(int l1, int l2, int l3)
    {
        // funcion declarada en <tuple> (C++11)
        std::tie(lados[0],lados[1],lados[2]) = std::tie(l1,l2,l3);

        // alternativa
        /*
        lados[0] = l1;
        lados[1] = l2;
        lados[2] = l3;
        */

        for( int i=0; i<3; i++ )
        {
          if( lados[i] == 0 )
            throw myexception("Uno de los lados es 0.");
          else if( lados[i] < 0 )
            throw myexception("Uno de los lados es negativo.");
          else if( lados[i] > lados[(i+1)%3] + lados[(i+2)%3] )
            throw myexception("La desigualdad triangular no se cumple.");
        }
    }

    int getLado1() const
    {
        return lados[0];
    }

    int getLado2() const
    {
        return lados[1];
    }

    int getLado3() const
    {
        return lados[2];
    }

    double area() const
    {
        double s, area;
        s = (getLado1() + getLado2() + getLado3()) / 2;
        area = sqrt(s*(s - getLado1())*(s - getLado2())*(s - getLado3()));
        return area;
    }

    int perimetro() const
    {
        return (getLado1() + getLado2() + getLado3());
    }

    void resize(int x) {

        switch (x) {
        case(2):
            for( int i=0; i<3; i++ )
              lados[i] *= x;

            break;
        case(-2):
             for( int i=0; i<3; i++ )
              lados[i] /= x;

            break;
        }

        cout << "Lado 1: " << getLado1() << "\nLado 2: " << getLado2() << "\nLado 3: " << getLado3() << endl;
    }
};

The list of changes is broad:

  • Class throws exceptions when detected
  • resize returns nothing
  • Query methods become const
  • A constructor that makes no sense is deleted

And with this, it would only be necessary to adapt the main :

int main() {
    Triangle tri;

    bool repetir;
    do {
      repetir = false;

      cout << "Introduzca los tres lados de un triangulo: ";
      int a, b, c;
      cin >> a >> b >> c;

      try
      {
        tri.setLados(a,b,c);
      }

      catch( std::exception const& e)
      {
        std::cout << e.what() << '\n';
        repetir = true;
      }
    } while( repetir );
    return 0;
}

I do not know if it shows, but now it's slightly cleaner.

    
answered by 08.11.2018 / 10:34
source
4

In the comments:

  

What I want is for the messages to be printed for the different exceptions, but I do not know how to do it

The way to throw an exception is:

throw objeto_excepcion;

The way to capture an exception is:

try
{
    // Código que lanza excepción
}
catch (const objeto_excepción &e)
{
    // Código para manejar la excepción, incluidos mensajes.
}

Therefore this code:

struct patata : public std::exception {};
struct frita : public std::exception {};
struct con : public std::exception {};
struct ketchup : public std::exception {};
struct y : public std::exception {};
struct mayonesa : public std::exception {};

int main()
{
    try
    {
        throw mayonesa{};
    }
    catch (const patata &e)
    {
        std::cout << "patata";
    }
    catch (const frita &e)
    {
        std::cout << "patata frita";
    }
    catch (const con &e)
    {
        std::cout << "patata frita con";
    }
    catch (const ketchup &e)
    {
        std::cout << "patata frita con ketchup";
    }
    catch (const y &e)
    {
        std::cout << "patata frita con ketchup y";
    }
    catch (const mayonesa &e)
    {
        std::cout << "patata frita con ketchup y mayonesa";
    }

    return 0;
}

It will print patata frita con ketchup y mayonesa because an exception of mayonesa was thrown.

To be taken into account.

  • You can throw the exceptions you want, but the capture must be a constant reference to the type thrown to avoid copies, do not throw ( throw ) pointers, even less if they are pointers to dynamic memory.
  • If you throw an exception in the constructor of an object, you must capture it in the constructor using a block try - catch at function level:

    Objeto::Objeto() try
    {
    }
    catch( ... )
    {
    }
    
answered by 08.11.2018 в 09:02