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 ;)
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!
Ahi va un pequeño template que hice yo para un programa de resolver circuitos lineales:
/*
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:
/*
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;
}
}
};
boost::numeric::ublas::matrix (http://www.boost.org/doc/libs/1_37_0/libs/numeric/ublas/doc/matrix.htm).
Cita de: davidgf en 22 de Febrero de 2009, 06:40:15 PM
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):
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 (http://www.boost.org/doc/libs/1_38_0/doc/html/array.html) (o similares), porque en la gran mayoría de casos el tamaño de las matrices lo conoces en tiempo de compilación.
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. ^_^'
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!
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().
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.
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...
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 (http://www.stratos-ad.com/forums/index.php?topic=10619.msg116335#msg116335).
Y el warning en cuestión posiblemente tenga su origen en código del cual no disponemos en este hilo.
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 (http://www.stratos-ad.com/forums/index.php?topic=10619.msg116335#msg116335).
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
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. ;)
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.
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 (http://www.stratos-ad.com/forums/index.php?topic=10619.msg116335#msg116335).
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.
Bueno, en Java las checked exceptions tienen el mismo problema: realmente solo pones las excepciones que tu sabes que van a salir (porque las lances tú o porque estén especificadas en algún método al que llamas) pero no tienen porque ser todas las excepciones que te pueden salir de ese método y te obligan a ensuciar el código de mala manera, además de que mucha gente que se dedica a capturarlas las silencia sin darse cuenta o se carga la pila de llamadas al relanzarlas (esto último no sé si es 100% seguro, hace años que no toco Java).
Un saludo,
Vicente
Cita de: lemoniac en 24 de Febrero de 2009, 06:04:17 PM
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.
Que sería de la vida del programador sin esos errores estúpidos que se convierten en totalmente invisibles a nuestros ojos. :-[ ¡Justamente era eso!. ¡Muchísimas gracias a todos! :D
PD. Y
davidgf, me serviste de inspiración, lo voy a hacer como una plantilla. :P ;)
Cita de: Vicente en 24 de Febrero de 2009, 09:00:13 PM... además de que mucha gente que se dedica a capturarlas las silencia sin darse cuenta o se carga la pila de llamadas al relanzarlas (esto último no sé si es 100% seguro, hace años que no toco Java)...
Ya que se encontro el problema, creo q no importa tanto si cambio el tema del post..........esto que comentas me intriga......que es eso de silenciarlas, y relanzarlas??
Silenciar una excepción: a veces la gente hace lo siguiente:
try
{
...
}
catch (LaExcepcion e)
{
...
}
Y se olvida del throw. Es una chorrada pero en proyectos grandes es un dolor de cabeza que no veas.
Relanzar una excepción (esto no estoy 100% seguro, en C# es así pero no recuerdo si es lo mismo en Java): a veces la gente hace:
try
{
...
}
catch (LaExcepcion e)
{
.... // Logs o cosas así
throw e;
}
Cuando en vez de "throw e;" deberían hacer "throw;" a secas. Al hacer throw e relanzas la excepción desde ese punto y te cargas toda la pila de llamadas de la excepción original. Al menos en C#, no sé si ocurre lo mismo en Java :)
Son dos "chorradas" pero a la hora de depurar son una putada. Además, que como dice Davur eso de poner las excepciones en la firma del método al final es poco más que un comentario glorificado que encima acopla el código.
Un saludo!
Vicente
Yo he tratado lo justo con excepciones, solamente las q java lanza por defecto, asi que mi coinocimiento quizás sea limitado.
Respecto a silenciar, en java hasta ahora no me he encontrado con ese problema, porque como tienes q decir implicitamente que excepciones puede lanzar un metodo si en un metodo que tiene try{}catch{} no le dices implicitamente que tiene q relanzarlas no puede, quieras o no, por lo q te ves obligado a responder a la excepcion. En el caso de que quieras que la excepcion vaya para abajo, con no poner el catch{} y definir a excepcion en el metodo es automatico, la excepcion pasa a quien llamo al metodo actual.
No se si me explico, realmente no le veo el uso..........quizas sea como comentaron y en java esto es un tema diferente y estoy hablando de mas.
Sobre relanzarlas......no estoy demasiado seguro, pero o manejas tu la excepcion o delegas en quien llamo a la funcion, pero creo q no puedes hacer ambas a la vez. No le veo mucho uso, pero me imagino que si en c no hay que defiicir implicitamente las excepciones de un metodo....puede q sea de utilidad.
De todos modos es "reconfortante" ver diferencias entre java y c (para bien o para mal, eso ya depende de cada uno) xDD
SALU2