Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Matriz invalida (C++)

Iniciado por Neodivert, 22 de Febrero de 2009, 04:47:21 PM

« anterior - próximo »

Neodivert

Buenas, estoy creando un programa en C++ para Windows, bajo el compilador MinGW y la IDE Code::Blocks. Para dicho programa estoy creando una clase cMatriz que por ahora tiene la siguiente pinta:


class cMatriz {
    private:
        int **M;
        int m, n;

        int Fila_Menor, Peso_Menor;
        int Mayor_num_Cifras;

        bool Matriz_Nula;
    public:
        // 1. Inicialización y destrucción.
        cMatriz( int i, int j );
        cMatriz( int i, int j, int min, int max );
        ~cMatriz();

        // 3. Operadores binarios
        cMatriz operator + ( cMatriz &B );

        // 4. Funciones gráficas.
        bool Nula(){ return Matriz_Nula; };
        int operator () ( int i, int j);
        friend ostream& operator << (ostream &Salida, cMatriz &Matriz);
};


Pero me acabo de dar cuenta de que, si quiero tener en cuenta todos los casos a la hora de inicializar una matriz, tendré que decidir que hacer si yo, o alguien que use la libreria intenta crear  una matriz con dimensiones invalidas (negativas o a cero). Por lo que se me plantearon 3 vías a desarrollar en el constructor, en el caso de que las dimensiones no sean validas:

a) Hacer un "delete this": Esta opción está casi descatarda, pues buscándola en google he leido que está desaconsejada por su comportamiento indefinido en el caso de que la uses en un objeto en vez de sobre un puntero a un objeto.

b) Lanzar una excepción: El Dios Google me ayuda de nuevo y me dirije a la siguiente página ( http://www.parashift.com/c++-faq-lite/exceptions.html ), donde leo lo siguiente:

Citar
[17.2] How can I handle a constructor that fails?

Throw an exception.

Constructors don't have a return type, so it's not possible to use return codes. The best way to signal constructor failure is therefore to throw an exception. If you don't have the option of using exceptions, the "least bad" work-around is to put the object into a "zombie" state by setting an internal status bit so the object acts sort of like it's dead even though it is technically still alive.

Siguiendo esta vía modifico el código del constructor así:


cMatriz::cMatriz( int i, int j ) : m(i), n(j), Fila_Menor(0), Peso_Menor(0), Mayor_num_Cifras(1), Matriz_Nula(true) {
    if( (m > 0) && (n > 0) ){
       // Inicializaciones oportunas de la matriz.
    }else{ // Matriz invalida.
        throw E_Matrices( E_RANGO_INVALIDO );
    }
}


Pero el compilador me lanza un warning: "control reaches end of non-void function". El cual no logro entender, pues se supone que el constructor no devulve ningún tipo, ¿no?  ???

c) Crear una variable booleana del tipo "Matriz_Valida" y usarla como bandera para saber si una matriz se creó correctamente y puede ser usada o no.

La 3ª opción me parece la más fácil, pero no se si es la más "moral" xD, o si hay alguna opción mejor o algún detalle que estoy pasando por alto.

Muchas gracias  ;)

wereoffs

Hola, creo que el diseño no es del todo correcto. Lo normal es hacer una clase virtual "cMatriz" con la definición de los métodos. Después creas otras clases que hereten de ésta para cada tipo de matriz (cMatriz2x2, cMatriz4x4). Después, si quieres dar la misma "libertad" al programador al construir una matriz, como en el constructor de cMatriz donde la pasas la filas y columnas, puedes crear por ejemplo una método estático en cMatriz (o una nueva classe con el/los métodos estáticos), que funcione como plantilla y devuelva el objeto correcto (cMatriz4x4 o cMatriz2x2) según los parámetros...

Espero haberme explicado bien, un saludo!

davidgf

#2
Ahi va un pequeño template que hice yo para un programa de resolver circuitos lineales:

Código (cpp) [Seleccionar]

/*
Matrices template by davidgf (www.davidgf.net)
*/

#include <iostream>
#include <vector>

template <typename T>

class Matrix {
private:
int rows, cols, cells;
std::vector <T> mat;
public:
Matrix(int n, int m) {
mat = std::vector <T> (n*m); rows = n; cols = m; cells = n*m;
for (int i = 0; i < cells; i++) mat[i] = T();
}
~Matrix() { }
void SetValue (int i, int j, T value) { mat[i*cols+j] = value; }
int NumCols () { return cols; }
int NumRows () { return rows; }

T operator() (int i, int j) { return mat[i*cols+j]; }

Matrix operator/( const T &a ) {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = this->mat[i] / a;
return ret;
}

// SUMA
Matrix operator+( const Matrix &a ) {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = a.mat[i] + this->mat[i];
return ret;
}
// CANVIO SIGNO
Matrix operator-() {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = -this->mat[i];
return ret;
}
// RESTA
Matrix operator-( const Matrix &a ) {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = this->mat[i] - a.mat[i];
return ret;
}
// MULTIPLICACIÓN
Matrix operator*( const Matrix &a ) {
Matrix<T> ret(this->rows,a.cols); // (a,b) * (b,c) = (a,c) (matrix rows, cols)
if (this->cols != a.rows) {
std::cout << "MATRIX ERROR: Cannot multiply the matrices because of their size!" << std::endl;
return ret;
}
for (int i = 0; i < this->rows; i++) {
for (int j = 0; j < a.cols; j++) {
T accum = T();
for (int k = 0; k < a.rows; k++) accum = accum + this->mat[i*this->cols+k]*a.mat[k*a.cols+j];
ret.mat[i*ret.cols+j] = accum;
}
}
return ret;
}
// SUMA ESCALAR
Matrix operator+( const T &a ) {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = this->mat[i] + a;
return ret;
}
// RESTA ESCALAR
Matrix operator-( const T &a ) {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = this->mat[i] - a;
return ret;
}
// MULT ESCALAR
Matrix operator*( const T &a ) {
Matrix<T> ret(rows,cols);
for (int i = 0; i < cells; i++) ret.mat[i] = this->mat[i]*a;
return ret;
}
// MATRIZ Adjunta A UN ELEMENTO
Matrix Ajoint (int r, int c) {
Matrix<T> ret(rows-1,cols-1);
if (this->cols != this->rows) {
std::cout << "MATRIX ERROR: Minor() calculation error, non-square matrix!" << std::endl;
return ret;
}

for (int i = 0, i2 = 0; i < rows; i++) {
if (i != r) {
for (int j = 0, j2 = 0; j < cols; j++) {
if (j != c) {
ret.mat[i2*ret.cols+j2] = this->mat[i*cols+j];
j2++;
}
}
i2++;
}
}
return ret;
}
// DETERMINANTE  (asume que la matriz es cuadrada ;)
T Det() {
if (this->cols == 1 and this->rows== 1) return this->mat[0];
if (this->cols != this->rows) {
std::cout << "MATRIX ERROR: Det() calculation error, non-square matrix!" << std::endl;
return T();
}
T accum = T();
bool osc = true;
for (int i = 0; i < this->cols; i++) {
if (osc) {
accum = accum + this->mat[i]*this->Ajoint(0,i).Det();
}else{
accum = accum - this->mat[i]*this->Ajoint(0,i).Det();
}
osc = not osc;
}
return accum;
}
Matrix Inverse () {
Matrix<T> ret(rows,cols);
if (this->cols != this->rows) {
std::cout << "MATRIX ERROR: Det() calculation error, non-square matrix!" << std::endl;
return ret;
}
if (this->cols == 1 and this->rows== 1) {
ret.mat[0] = T(1) / this->mat[0];
return ret;
}
for (int i = 0; i < this->cols; i++) {
for (int j = 0; j < this->cols; j++) {
if ( (i+j) % 2 == 0) ret.mat[i*cols+j] = this->Ajoint(i,j).Det();
else ret.mat[i*cols+j] = -this->Ajoint(i,j).Det();
}
}
return ret/this->Det();
}

void Print () {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
std::cout << this->mat[i*cols+j].real << " " << this->mat[i*cols+j].imag << "i   ";
}
std::cout << std::endl;
}
}


};



Es un template asi que puedes usarla con qualquier tipo de datos, int, double, float... etc
Yo la uso con complejos:

Código (cpp) [Seleccionar]

/*
Complex Numbers template by davidgf (www.davidgf.net)
*/

#include <iostream>
#include <vector>
#include <math.h>

template <typename T>

class Complex {
public:
T real, imag;

Complex() { real=0; imag=0; }
Complex (T real, T imag) { this->real = real; this->imag = imag; }
Complex (T real) { this->real = real; this->imag = 0; }
~Complex() {}

// SUMA
Complex Conj() const { return Complex (real, -imag); }
Complex ModQ() const { return Complex (real*real+imag*imag, T()); }

// SUMA
Complex operator+( const Complex &a ) { return Complex (real+a.real, imag+a.imag); }
// CANVIO SIGNO
Complex operator-() { return Complex (-real, -imag); }
// RESTA
Complex operator-( const Complex &a ) { return Complex (real-a.real, imag-a.imag); }
// MULTIPLICACIÓN
Complex operator*( const Complex &a ) { return Complex(real*a.real-imag*a.imag, real*a.imag+a.real*imag); }
// DIV
Complex operator/( const Complex &a ) {
T den = a.ModQ().real;
Complex ret = *this * a.Conj();
ret.real = ret.real / den;
ret.imag = ret.imag / den;
return ret;
}

T Arg () const {
if (imag > T() and real > T()) {  // 1r q
return (T)atan(imag/real)*(T)180/M_PI;
}else if (imag > T() and real < T()) {  // 2n q
return (T)atan(imag/real)*(T)180/M_PI+(T)180;
}else if (imag < T() and real < T()) {  // 3r q
return (T)atan(imag/real)*(T)180/M_PI+(T)180;
}else{  // 4t q
return (T)atan(imag/real)*(T)180/M_PI+(T)360;
}
}


};
Tàrraco: una aventura por la Tarragona romana (http://tarraco.davidgf.net)

davur

boost::numeric::ublas::matrix.

Cita de: davidgf en 22 de Febrero de 2009, 06:40:15 PM
Código (cpp) [Seleccionar]

class Matrix
{
       // ...

Matrix(int n, int m)
       {
mat = std::vector <T> (n*m); rows = n; cols = m; cells = n*m;
for (int i = 0; i < cells; i++) mat[i] = T();
}

       // ...
};


Puedes inicializar la matriz de una manera más eficiente, utilizando la lista de inicialización, evitando crear un vector temporal y su copia al vector miembro y eliminando el bucle (el constructor de std::vector ya llama al constructor por defecto de sus elementos):

Código (cpp) [Seleccionar]

class Matrix
{
       // ...

Matrix(int n, int m) : rows(n), cols(m), cells(n*m)
       {
                mats.resize(cells);
}

       // ...
};


Personalmente, yo no utilizaría std::vector para almacenar los elementos de la matriz, sino boost::array (o similares), porque en la gran mayoría de casos el tamaño de las matrices lo conoces en tiempo de compilación.

Neodivert


Muchas gracias amigos, pero no pretendo que me den las soluciones, sino solo resolverme las dudas que tengo con las vías b) y c) que se me ocurrieron.  ^_^'

davidgf

Aps perdona, pero yo lo hacía para que vieras una implementación ya funcional y pudieras comparar o ver cómo lo hago yo.
Inspiración, ya me entiendes ;)

Saludos!
Tàrraco: una aventura por la Tarragona romana (http://tarraco.davidgf.net)

davur

#6
Cita de: Neodivert en 22 de Febrero de 2009, 10:48:47 PM

Muchas gracias amigos, pero no pretendo que me den las soluciones, sino solo resolverme las dudas que tengo con las vías b) y c) que se me ocurrieron.  ^_^'

a) y c) no tienen sentido muy generalmente, y desde luego no son soluciones idiomáticas en C++.

Respecto a b), como bien citas, la manera idiomática en C++ de señalar errores en un constructor es lanzando una excepción. El warning que comentas, dado el trozo de código que citas, me parece extraño.

Una cuarta opción que puedes plantearte, por completitud, es utilizar assert().

shephiroth

Buenas. No se como van las excepciones en c, pero en java tienes q indicar el método que excepciones puede lanzar (asi sin ver el codigo sabes q excepciones tienes q controlar al crear el objeto).....quizas por eso te da el warning.

Vicente

Cita de: shephiroth en 23 de Febrero de 2009, 10:41:53 PM
Buenas. No se como van las excepciones en c, pero en java tienes q indicar el método que excepciones puede lanzar (asi sin ver el codigo sabes q excepciones tienes q controlar al crear el objeto).....quizas por eso te da el warning.

Mmm, yo creo que C++ no tiene checked exceptions...

davur

Cita de: shephiroth en 23 de Febrero de 2009, 10:41:53 PM
Buenas. No se como van las excepciones en c, pero en java tienes q indicar el método que excepciones puede lanzar (asi sin ver el codigo sabes q excepciones tienes q controlar al crear el objeto).....quizas por eso te da el warning.

En C++ es posible especificar excepciones en funciones, pero no es útil ni recomendable.

davur

Y el warning en cuestión posiblemente tenga su origen en código del cual no disponemos en este hilo.

shephiroth

Cita de: davur en 23 de Febrero de 2009, 11:44:06 PM
Cita de: shephiroth en 23 de Febrero de 2009, 10:41:53 PM
Buenas. No se como van las excepciones en c, pero en java tienes q indicar el método que excepciones puede lanzar (asi sin ver el codigo sabes q excepciones tienes q controlar al crear el objeto).....quizas por eso te da el warning.

En C++ es posible especificar excepciones en funciones, pero no es útil ni recomendable.

Como dije no se como va el tema de excepciones en c, por lo q no comentaré el post que linkeas. Lo que si dire, mi comentario no iba encaminado a posible optimizacion del compilador, sino a claridad de codigo. Tu imaginate una clase madre que lance excepciones, y tu sin saberlo heredas de uno de sus bisnietos sin tener ni idea de que excepciones lanza. Sin embargo si en el constructor dejas claro que excepciones lanza ya puede ser nieto o tatatatataranieto, que siempre aparecera declarado en el costructor de la clase que de la que heredas (o donde esta declarado el superconstrunstor al q llames).

A parte (esto ya es offtopic) en java te obliga a ponerlo xDD

Neodivert

Cita de: davur en 23 de Febrero de 2009, 11:46:09 PM
Y el warning en cuestión posiblemente tenga su origen en código del cual no disponemos en este hilo.

El fichero Clase_Matriz.cpp contiene lo siguiente:


/*
  Nombre: Matrices v1.0
  Autor: Moises Bonilla (Neodivert)
  Fecha de inicio: 15/02/2009
  Fecha de finalización:
  Descripcion: Utilidad generadora de ejercicios aleatorios para el cálculo con matrices.
*/

#include<cstdlib>
#include"E_Matrices.cpp"


/*                             Constantes y variables globales                                                                    */
/***********************************************************************************************/


/*                                 Prototipos de funciones                                                                               */
/***********************************************************************************************/

class cMatriz {
    private:
        int **M;
        int m, n;

        int Fila_Menor, Peso_Menor;
        int Mayor_num_Cifras;

        bool Matriz_Nula;
    public:
        // 1. Inicialización y destrucción.
        cMatriz( int i, int j );
        cMatriz( int i, int j, int min, int max );
        ~cMatriz();

        // 3. Operadores binarios
        cMatriz operator + ( cMatriz &B );

        // 4. Funciones gráficas.
        bool Nula(){ return Matriz_Nula; };
        int operator () ( int i, int j);
        friend ostream& operator << (ostream &Salida, cMatriz &Matriz);
};

/*                                Definiciones de funciones                                                                           */
/***********************************************************************************************/

cMatriz::cMatriz( int i, int j ) : m(i), n(j), Fila_Menor(0), Peso_Menor(0), Mayor_num_Cifras(1), Matriz_Nula(true) {
    if( (m > 0) && (n > 0) ){
        M = new int*[m];
        for( i=0; i<m; ++i ){
            M[i] = new int(n);
            for( j=0; j<n; j++ ){
                M[i][j] = 0;
            }
        }
    }else{ // Matriz invalida.
        throw E_Matrices( E_RANGO_INVALIDO );
    }
}

cMatriz::cMatriz( int i, int j, int min, int max ) : m(i), n(j), Fila_Menor(0), Peso_Menor(0),
                                Mayor_num_Cifras(1), Matriz_Nula(false) {
    int num_Cifras = 0;
    int Peso = 0;

    if( !min ) min++;

    if( (m > 0) && (n > 0) ){
        M = new int*[m];
        for( i=m-1; i>=0; --i ){
            M[i] = new int[n];
        }

        for( i=m-1; i>=0; --i ){
            for( j=n-1; j>=0; --j ){
                M[i][j] = ( min + (rand() % (max)) );
                if( M[i][j] ){
                    Matriz_Nula = false;
                }
                Peso += M[i][j];
                num_Cifras = Num_Cifras( M[i][j] );
                if( num_Cifras > Mayor_num_Cifras ){
                    Mayor_num_Cifras = num_Cifras;
                }

            }

            Peso /= n;
            if( Peso < 0 ){
                    Peso = -Peso;
            }
            if( ( Peso < Peso_Menor ) || ( i==m-1 ) ){
                Peso_Menor = Peso;
                Fila_Menor = i;
            }
            Peso = 0;
        }
    }else{ // Matriz invalida.
        throw E_Matrices( E_RANGO_INVALIDO );
    }
}

...


El fichero que incluyo, el "E_Matrices.cpp" contiene lo siguiente:


#include"Generales.h"


enum EXCEPCIONES {
    E_RANGOS_DIFERENTES, E_RANGO_INVALIDO
};

class E_Matrices: public exception {
    private:
        int Motivo;
    public:
        E_Matrices( int Motivox ) : exception(), Motivo(Motivox) {};
        const char* what() const throw(){
            switch( Motivo ){
                case E_RANGOS_DIFERENTES:
                    return "ERROR: Los rangos de las matrices a operar son distintos.";
                break;
                case E_RANGO_INVALIDO:
                    return "ERROR: Rangos de la matriz invalidos";
                break;
            }
        }
};


Lo curioso es que el warning me lo lanza en el primer fichero, el "Clase_Matriz.cpp" en la línea 22, la cual es:


class cMatriz {
    private:
        int **M; -- Línea 22
        int m, n;


Gracias.  ;)

lemoniac

Sobre el warning, fíjate en el #include"E_Matrices.cpp".

Realmente te está diciendo que el método what de E_Matrices no siempre devuelve un valor.

davur

#14
Cita de: shephiroth en 24 de Febrero de 2009, 01:11:19 AM
Cita de: davur en 23 de Febrero de 2009, 11:44:06 PM
Cita de: shephiroth en 23 de Febrero de 2009, 10:41:53 PM
Buenas. No se como van las excepciones en c, pero en java tienes q indicar el método que excepciones puede lanzar (asi sin ver el codigo sabes q excepciones tienes q controlar al crear el objeto).....quizas por eso te da el warning.

En C++ es posible especificar excepciones en funciones, pero no es útil ni recomendable.

Como dije no se como va el tema de excepciones en c, por lo q no comentaré el post que linkeas. Lo que si dire, mi comentario no iba encaminado a posible optimizacion del compilador, sino a claridad de codigo. Tu imaginate una clase madre que lance excepciones, y tu sin saberlo heredas de uno de sus bisnietos sin tener ni idea de que excepciones lanza. Sin embargo si en el constructor dejas claro que excepciones lanza ya puede ser nieto o tatatatataranieto, que siempre aparecera declarado en el costructor de la clase que de la que heredas (o donde esta declarado el superconstrunstor al q llames).

A parte (esto ya es offtopic) en java te obliga a ponerlo xDD

El tema es que, de las especificaciones de excepciones, uno espera que garantizen que la función en cuestión solo lanza excepciones determinadas (o ninguna). Pero en C++ esto no es así: las especificaciones de excepciones refuerzan en tiempo de ejecución que la función en cuestión sólo lanza excepciones determinadas (o ninguna).

El matiz entre garantizar y reforzar en tiempo de ejecución, unido al hecho de que las supuestas optimizaciones que uno podría esperar (y que no son tales), convierten a las especificaciones de excepciones en poco más que comentarios glorificados con una penalización en el tiempo de ejecución. Además, las especificaciones de excepciones incrementan el acoplamiento entre clases de una misma jerarquía (un cambio en la especificación de excepciones de una función virtual en una clase base puede afectar a muchas clases derivadas...).

Las especificaciones de excepciones son una de esas ideas de C++ que, con el tiempo, se ha comprobado que no eran buenas (como tampoco lo es export para templates, por ejemplo).

Eso sí, como bien apuntas, en otros lenguajes nada de esto es aplicable.






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.