Generalmente se termina con un código como este para los objetos en la pila:
MyClassWithNoThrowConstructor foo;
if (foo.init(bar, baz, etc) != 0) {
// error-handling code
} else {
// phew, we got away with it. Now for the next object...
}
Y esto por objetos en el montón.Asumo que los redefina operador global nuevo con algo que devuelve NULL en lugar de tirar, para ahorrarse recordando a utilizar nothrow nueva en todas partes:
MyClassWithNoThrowConstructor *foo = new MyClassWithNoThrowConstructor();
if (foo == NULL) {
// out of memory handling code
} else if (foo->init(bar, baz, etc) != 0) {
delete foo;
// error-handling code
} else {
// success, we can use foo
}
Obviamente, si le sea posible, utilizar punteros inteligentes para ahorrar tener que recordar las eliminaciones, pero si su compilador no admite las excepciones correctamente, entonces puede tener problemas para obtener Boost o TR1. No lo sé.
También es posible que desee estructurar la lógica de forma diferente, o abstracción de la combinación de new e init, para evitar el "código de flecha" profundamente anidado cuando maneje objetos múltiples, y para hacer común el manejo de errores entre dos casos de falla. Lo anterior es solo la lógica básica en su forma más minuciosa.
En ambos casos, el constructor establece todo a los valores predeterminados (puede tomar algunos argumentos, siempre que lo que hace con esos argumentos no puede fallar, por ejemplo, si simplemente los almacena). El método init puede hacer el trabajo real, que puede fallar, y en este caso devuelve 0 éxito o cualquier otro valor por falla.
es probable que necesite para hacer cumplir que cada método init lo largo de toda su base de código informa de errores de la misma manera: que haces no quieren algunos regresando 0 éxito o un código de error negativo, algunos 0 éxito devolución o un código de error positivo, algunos devolviendo bool, algunos devolviendo un objeto por valor que tiene campos que explican la falla, algunos configuran errno global, etc.
Quizás pueda echar un vistazo rápido a algunos documentos API de la clase Symbian en línea. Symbian usa C++ sin excepciones: tiene un mecanismo llamado "Leave" que lo compensa parcialmente, pero no es válido para abandonar desde un constructor, por lo que tiene el mismo problema básico en términos de diseñar constructores que no fallan y diferir el error operaciones para iniciar rutinas. Por supuesto, con Symbian, se permite que la rutina init se vaya, por lo que la persona que llama no necesita el código de manejo de errores que indiqué anteriormente, pero en términos de división de trabajo entre un constructor C++ y una llamada init adicional, es lo mismo.
principios generales incluyen:
- Si el constructor quiere obtener un valor de algún lugar de una manera que puede fallar, que aplazar al inicio y dejar el valor por defecto inicializa en el ctor.
- Si su objeto tiene un puntero, configúrelo como nulo en el ctor y ajústelo "correctamente" en el init.
- Si su objeto contiene una referencia, cambie a un puntero (inteligente) para que pueda comenzar con nulo, o haga que la persona que llama pase el valor al constructor como un parámetro en lugar de generarlo en el ctor.
- Si su constructor tiene miembros de tipo de objeto, entonces está bien. Sus codificadores tampoco lanzarán, por lo que está perfectamente bien construir sus miembros (y clases base) en la lista de inicializadores de la forma habitual.
- Asegúrese de hacer un seguimiento de lo que está configurado y lo que no, para que el destructor funcione cuando falla el init.
- Todas las funciones que no sean constructores, el destructor e init pueden suponer que init tuvo éxito, siempre que documente para su clase que no es válido llamar a ningún método que no sea init hasta que init haya sido llamado y haya tenido éxito.
- Puede ofrecer varias funciones init, que a diferencia de los constructores pueden llamarse entre sí, del mismo modo que para algunas clases ofrecería múltiples constructores.
- No puede proporcionar conversiones implícitas que pueden fallar, por lo que si su código actualmente se basa en conversiones implícitas que generan excepciones, entonces debe rediseñar.Lo mismo ocurre con la mayoría de las sobrecargas del operador, ya que sus tipos de retorno están restringidos.
Algunas partes del trabajo de refuerzo pero ya tenemos nuestro propio puntero inteligente, así que eso no es un problema. Hasta ahora no estamos considerando un problema de std :: bad_alloc porque si nos quedamos sin memoria tenemos problemas mayores. ¡Gracias por todas las buenas sugerencias! –
En esta situación, usando if/else anidado para verificar si hay errores, usando la palabra clave goto se puede usar para reducir el depósito de anidación Esto hace que el código sea más fácil para el ojo. Entonces, yo haría "if (something failed) goto error_handler;" – Skizz
Probablemente valga la pena señalar que este patrón se conoce como "construcción en dos fases" – ChrisN