2009-06-30 20 views
5

Ésta parece ser una cuestión trivial, pero me quedó colgado en él durante unas cuantas horas (quizás demasiado Java mató a mi C++ braincells).manera correcta para inicializar un objeto con excepción constructor tirar

He creado una clase que tiene el siguiente constructor (es decir, ningún constructor por defecto)

VACaptureSource::VACaptureSource(std::string inputType, std::string inputLocation) { 
    if(type == "" || location == "") { 
    throw std::invalid_argument("Empty type or location in VACaptureSource()"); 
} 
type = inputType; 
location = inputLocation; 

// Open the given media source using the appropriate OpenCV function. 
if(type.compare("image")) { 
    frame = cvLoadImage(location.c_str()); 
    if(!frame) { 
     throw std::runtime_error("error opening file"); 
    } 
} 
else { 
    throw std::invalid_argument("Unknown input type in VACaptureSource()"); 
} 

}

Cuando quiero crear una instancia, utilizo

// Create input data object 
try { 
    VACaptureSource input = VACaptureSource("image", "/home/cuneyt/workspace/testmedia/face_images/jhumpa_1.jpg"); 
} 
catch(invalid_argument& ia) { 
    cerr << "FD Error: " << ia.what() << endl; 
    usage(argv[0]); 
} 
catch(runtime_error& re) { 
    cerr << "FD Error: " << re.what() << endl; 
    usage(argv[0]); 
} 

Sin embargo , en este caso, la instancia es local para este bloque y no puedo referirme a él en ningún otro lado. Por otra parte, no puedo decir

VACAptureSource input; 

al inicio del programa ya que no hay constructor por defecto.

Cuál es la forma correcta de hacer esto?

Gracias!

+1

¿Cómo resolverías esto en Java? Aplica la misma solución. Este problema no depende del idioma.Diferentes idiomas solo ofrecen una sintaxis diferente para propósitos equivalentes, considere que cuando define una variable en Java no está declarando un objeto sino un _reference_ (puntero en terminología C++), por lo que el código que parece similar en Java y C++ no es realmente código equivalente. –

Respuesta

8

¿Qué pasa con el uso de un puntero (o alguna versión RAII del mismo)?

VACaptureSource* input = NULL; 

try { 
    input = new VACaptureSource(...); 
} catch(...) { 
    //error handling 
} 

//And, of course, at the end of the program 
delete input; 
+1

Gracias, pensé en eso, pero pensé que apuntaba "mal" en C++, es decir, que lo usaba solo como último recurso. ¿No hay otra manera que usar punteros? – recipriversexclusion

+3

"Mal" es una palabra bastante fuerte. Sin embargo, como sugiere CAdaker, una variante RAII (es decir, autodestrucción), como std :: auto_ptr o cualquier puntero automático Boost apropiado, sería una opción sensata. –

+0

Por cierto, es probable que solo desee utilizar instancias válidas, lo que significa que es posible que desee volver a lanzar desde los bloques catch después de haber hecho lo que se debe hacer. –

4

Una variable local se asigna al bloque en el que está asignada (como Java) pero se destruirá tan pronto como finalice el bloque (a diferencia de Java) por lo que debe hacer todo lo que desee en el bloque try (que puede no ser deseable si solo desea manejar excepciones de constructor) o debe asignar el objeto a otro lugar (por ejemplo, montón) y usar un puntero en el bloque principal para acceder a él.

2

Es posible utilizar un puntero

VACaptureSource* input; 
// Create input data object 
try { 
    input = new VACaptureSource("image", "/home/cuneyt/workspace/testmedia/face_images/jhumpa_1.jpg"); 
} 
catch(invalid_argument& ia) { 
    cerr << "FD Error: " << ia.what() << endl; 
    usage(argv[0]); 
} 
catch(runtime_error& re) { 
    cerr << "FD Error: " << re.what() << endl; 
    usage(argv[0]); 
} 

y que necesita para liberar el objeto cuando termine de usarlo

delete input 
+1

De nuevo, no veo el nedd para un puntero. –

11

¿por qué necesita hacer referencia a ella fuera de la try ¿bloquear?

En lugar de

try { 
    VACaptureSource input = VACaptureSource("image", "/home/cuneyt/workspace/testmedia/face_images/jhumpa_1.jpg"); 
} 
//catch.... 

//do stuff with input 

se podía mover todo en el bloque try:

try { 
    VACaptureSource input = VACaptureSource("image", "/home/cuneyt/workspace/testmedia/face_images/jhumpa_1.jpg"); 
    //do stuff with input 
} 
//catch.... 

o se puede factorizar a cabo en una función separada, que se llama desde el bloque try:

void doStuff(VACaptureSource& input){ 
    //do stuff with input 
} 

try { 
    VACaptureSource input = VACaptureSource("image", "/home/cuneyt/workspace/testmedia/face_images/jhumpa_1.jpg"); 
    doStuff(input); 
} 
//catch.... 

el último de ellos incluso le da la buena ventaja de separar la construcción de uso, lo cual se ubica muy bien en pruebas unitarias en las que es posible que desee que la función funcione en un objeto simulado en su lugar.

+0

Otra buena respuesta suya con varias alternativas incluidas. Me gustan estos :) Pero para ser justos y dar una razón posible, también a menudo quería que esas cosas en un bloque de "prueba" se filtraran en el alcance adjunto. Pero esto podría ser peligroso, considere este código: try {string a;/* <- supongamos que arroja */cadena b; } catch (...) {} b.size();/* ¡Uy, b aún no construido! */ –

+0

Sí, a menudo quiero hacer lo mismo. Dado que no hay una forma obvia de hacerlo, tenemos que conformarnos con las soluciones. – jalf

+0

También existe la posibilidad de querer capturar el argumento std :: invalid_argument del constructor, pero deja que todas las excepciones se propaguen desde el resto del código. Considere un método para obtener un valor que pueda arrojar, pero que en esta pieza específica de código tiene un valor predeterminado en caso de que la operación falle. Más tarde, desea utilizar ese valor con otro método que también arroje la misma excepción, pero no tiene una forma sensata de continuar su flujo en ese caso. Desea detectar la primera excepción, pero no la última. –

1

¿Qué pasa con la adición de un constructor por defecto que deja el objeto en un estado no configurado especial? Luego tenga una función create() para realmente crearlo.

entonces usted puede hacer:

VACaptureSource input; 
try 
{ 
    input.create("image", "..."); 
} 
catch(...) 
{ 
    ... 
} 

Dependiendo de la situación esto podría ser mejor que jugar con los punteros. A pesar de que entonces también tiene que comprobar si el create() se llama en realidad antes de hacer algo ...

+3

Eso es muy similar a Java. En C++, intenta poner el objeto en un estado utilizable en el constructor. Al hacerlo de esta manera, necesita mantener información de estado sobre el clima en el que se ha creado el objeto y esta información de estado se filtrará a todos los otros métodos. Esto hace que el código sea más feo (más oloroso). –

+0

No. Los objetos están construidos o no construidos. No hay término medio. Un constructor arrojando significa que el objeto no se puede usar en ese estado y, por lo tanto, lo más sensato es no tener ese objeto vivo. Si lo hace, comenzará a agregar flujos de código con objetos aparentemente correctos que no son los que tendrá que verificar y probar, y habrá muchos más lugares donde puede cometer errores, y en un punto, no importa cuán bueno sea usted cometerá un error, sucederá muy lejos del punto de creación y tendrá una buena noche de depuración tardía. –

+0

@Martin York: No, no es muy similar a Java. Al estilo de Java está definiendo un puntero que se inicializa a nulo/NULL/0 y luego se asigna en el montón con nuevo. Java no es un lenguaje sin puntero, solo un lenguaje sin puntero-aritmética, donde los punteros (por el nombre de referencia) se arrastran en cada línea de código. –

1

Yo en realidad no veo ningún probelms aquí:

Un par de cosas que actualizaría:

  • Captura excepciones por referencia de referencia.
  • El compilador puede optimizar la construcción de copia en su código
    Pero se ve mejor sin él. Simplemente declare la entrada con sus parámetros.
  • Refactorizaría el constructor para tomar los parámetros de referencia const
    Y los inicializaría en la lista de inicializadores.
  • También me aseguraría de que el miembro 'frame' sea realmente un puntero inteligente.

Así que haría esto (para mayor claridad).

VACaptureSource::VACaptureSource(std::string const& inputType, 
            std::string const& inputLocation) 
     :type(inputType) 
     ,location(inputLocation) 
{ 
    // Other Code that throws. 
} 
void playWithCode() 
{ 
    // Get input information from user. 
    VACaptureSource input("image", "/home/cuneyt/workspace/testmedia/face_images/jhumpa_1.jpg"); 

    // use the input object. 
    // Do not need to play with pointers here. 
} 
int main() 
{ 
    try 
    { 
     playWithCode(); 
    } 
    catch(invalid_argument const& ia) 
    { cerr << "FD Error: " << ia.what() << endl; 
     usage(argv[0]); 
    } 
    catch(runtime_error const& re) 
    { cerr << "FD Error: " << re.what() << endl; 
     usage(argv[0]); 
    } 
} 
+0

El problema aquí es que todas las excepciones invalid_argument en playWithCode se capturan al mismo tiempo con la misma semántica. Podría ser el caso donde en el 'usuario el objeto de entrada' otra operación puede arrojar un argumento inválido que desea procesar de manera diferente. –

+0

Lo siento, pero no puedo hacer cabezas ni rastros de eso. –

+0

Digamos que quiere crear un VACaptureSource, pero puede recurrir a otro CaptureSource si eso falla y luego desea utilizarlo en una llamada que puede lanzar std :: runtime_error, pero no puede recuperarse de un error allí. Debería intentar crear VACaptureSource, detectar excepciones y continuar, pero deje que std :: runtime_error se propague por la pila si se lanza más adelante en el método 'playWithCode()'. –

3

¿Puedo observar que casi cualquier pero el constructor más trivial se puede esperar que una excepción. Por lo tanto, no debe considerar las excepciones como "especiales" en algún sentido, sino que debe escribir su código para que se ocupe de ellas de forma natural. Esto significa usar RAII y las otras técnicas que otras respuestas aquí han sugerido.

+0

No es una respuesta, pero es un buen punto para razonar en el problema de manejo de excepciones generales. –

0

Simple. No arrojes excepciones dentro de un constructor. No solo tiene que envolver el constructor en el bloque try, no podrá manejar la memoria muy bien en el caso de que detecte una excepción (¿llama al destructor? ¿Cuánta memoria de la clase necesita eliminarse?)

UPDATE0: Aunque no estoy seguro de si la administración de la memoria es un problema si está utilizando una instancia.

Update1: Hmmm, tal vez estoy pensando acerca de las excepciones en destructores.

int 
main2(int argc, char* argv[]) 
{ 
    MyClass class; 
    class.doSomething(); 
} 

int 
main(int argc, char* argv[]) 
{ 
    int result = 0; 
    try { 
     main2(argc, argv); 
    } catch (std::exception& se) { 
     // oh noes! 
     result = 1; 
    } 
    return result; 
} 
+1

C++ tiene una regla sobre lanzar excepciones desde un constructor: cualquier subobjeto creado antes de la excepción se destruye adecuadamente, pero el objeto cuyo constructor falló nunca se creó y no se destruye. Y toda la memoria asignada para el objeto es desasignada. –

+0

@ Max Lybbert - Gracias. He aplicado la regla "nunca arrojar excepciones en destructores" a los constructores antes, tal vez lo recordaré ahora. –

2

No puedo decir

VACaptureSource input; 

al inicio del programa ya que no hay constructor por defecto.

Hay una buena razón por la que no creó un constructor predeterminado: a saber, que un VACaptureSource solo tiene sentido cuando se asocia con un archivo. Por lo tanto, no cree un constructor predeterminado. En su lugar, simplemente reconozca que el alcance del objeto VACaptureSource es el bloque try, y úselo dentro de allí.

Cuestiones relacionadas