Tengo una clase CPP cuyo constructor hace algunas operaciones. Algunas de estas operaciones pueden fallar. Sé que los constructores no devuelven nada.
Mis preguntas son
¿Está permitido hacer algunas operaciones que no sean inicializar miembros en un constructor?
¿Es posible decirle a la función de llamada que algunas operaciones en el constructor han fallado?
¿Puedo hacer que
new ClassName()
return NULL si se producen algunos errores en el constructor?
object-oriented
c++
MayurK
fuente
fuente
Square
, con un constructor que toma un parámetro, la longitud de un lado, desea verificar si ese valor es mayor que 0.Respuestas:
Sí, aunque algunos estándares de codificación pueden prohibirlo.
Sí. La forma recomendada es lanzar una excepción. Alternativamente, puede almacenar la información de error dentro del objeto y proporcionar métodos para acceder a esta información.
No.
fuente
Puede crear un método estático que realice el cálculo y devuelva un objeto en caso de éxito o no en caso de error.
Dependiendo de cómo se haga esta construcción del objeto, podría ser mejor crear otro objeto que permita la construcción de objetos en un método no estático.
Llamar a un constructor indirectamente a menudo se denomina "fábrica".
Esto también le permitiría devolver un objeto nulo, que podría ser una mejor solución que devolver nulo.
fuente
NULL
. Por ejemplo, enint foo() { return NULL
realidad devolvería0
(cero), un objeto entero. Enstd::string foo() { return NULL; }
accidentalmente llamaríasstd::string::string((const char*)NULL)
cuál es Comportamiento indefinido (NULL no apunta a una cadena terminada en \ 0).int
. Por ejemplo,std::allocator<int>
es una fábrica perfectamente sana.@SebastianRedl ya dio las respuestas simples y directas, pero alguna explicación adicional podría ser útil.
TL; DR = hay una regla de estilo para mantener a los constructores simples, hay razones para ello, pero esas razones se relacionan principalmente con un estilo de codificación histórico (o simplemente malo). El manejo de excepciones en los constructores está bien definido, y los destructores aún se llamarán para miembros y variables locales completamente construidas, lo que significa que no debería haber ningún problema en el código idiomático de C ++. La regla de estilo persiste de todos modos, pero normalmente eso no es un problema: no toda la inicialización tiene que estar en el constructor, y particularmente no necesariamente ese constructor.
Es una regla de estilo común que los constructores deben hacer el mínimo absoluto posible para configurar un estado válido definido. Si su inicialización es más compleja, debe manejarse fuera del constructor. Si no hay un valor económico para inicializar que su constructor pueda configurar, debe debilitar a los invasores forzados por su clase para agregar uno. Por ejemplo, si asignar almacenamiento para que su clase lo administre es demasiado costoso, agregue un estado nulo aún no asignado, porque, por supuesto, tener estados de casos especiales como nulo nunca causó problemas a nadie. Ejem.
Aunque es común, ciertamente en esta forma extrema está muy lejos de ser absoluto. En particular, como indica mi sarcasmo, estoy en el campamento que dice que debilitar a los invariantes es casi siempre un precio demasiado alto. Sin embargo, hay razones detrás de la regla de estilo, y hay formas de tener constructores mínimos e invariantes fuertes.
Las razones se relacionan con la limpieza automática del destructor, particularmente frente a excepciones. Básicamente, tiene que haber un punto bien definido cuando el compilador se hace responsable de llamar a los destructores. Mientras todavía está en una llamada de constructor, el objeto no está necesariamente completamente construido, por lo que no es válido llamar al destructor para ese objeto. Por lo tanto, la responsabilidad de destruir el objeto solo se transfiere al compilador cuando el constructor se completa con éxito. Esto se conoce como RAII (asignación de recursos es la inicialización), que no es realmente el mejor nombre.
Si un tiro excepción se produce dentro del constructor, las necesidades de piezas construidas por cualquier cosa que hay que limpiar explícitamente, por lo general en una
try .. catch
.Sin embargo, los componentes del objeto que ya se han construido con éxito son responsabilidad del compilador. Esto significa que, en la práctica, no es realmente un gran problema. p.ej
El cuerpo de este constructor está vacío. En tanto que los constructores para
base1
,member2
ymember3
son una excepción segura, no hay nada de qué preocuparse. Por ejemplo, si el constructor demember2
tiros, ese constructor es responsable de limpiarse. La basebase1
ya estaba completamente construida, por lo que su destructor se llamará automáticamente.member3
nunca fue construido parcialmente, por lo que no necesita limpieza.Incluso cuando hay un cuerpo, las variables locales que se construyeron completamente antes de que se lanzara la excepción se destruirán automáticamente, al igual que cualquier otra función. Los cuerpos de constructor que hacen juegos malabares con punteros en bruto, o "poseen" algún tipo de estado implícito (almacenado en otro lugar), lo que generalmente significa que una llamada de función de inicio / adquisición debe coincidir con una llamada de finalización / liberación, puede causar problemas de seguridad excepcionales, pero el problema real allí está fallando en administrar un recurso adecuadamente a través de una clase. Por ejemplo, si reemplaza punteros sin formato con
unique_ptr
en el constructor,unique_ptr
se llamará automáticamente al destructor para si es necesario.Todavía hay otras razones que la gente da para preferir constructores de hacer lo mínimo. Uno es simplemente porque existe la regla de estilo, muchas personas suponen que las llamadas de constructor son baratas. Una forma de tener eso, pero aún tener invariantes fuertes, es tener una clase de fábrica / constructor separada que tenga invariantes debilitados en su lugar, y que establezca el valor inicial necesario usando (potencialmente muchas) llamadas de función miembro normales. Una vez que tenga el estado inicial que necesita, pase ese objeto como argumento al constructor de la clase con los invariantes fuertes. Eso puede "robar las entrañas" del objeto de invariantes débiles, mover la semántica, que es una
noexcept
operación barata (y generalmente ).Y, por supuesto, puede incluir eso en una
make_whatever ()
función, por lo que las personas que llaman de esa función nunca necesitan ver la instancia de clase de invariantes debilitados.fuente
main
función o variables estáticas / globales. Un objeto asignado usandonew
, no es propiedad hasta que asigne esa responsabilidad, pero los punteros inteligentes poseen los objetos asignados en el montón a los que hacen referencia, y los contenedores poseen sus estructuras de datos. Los propietarios pueden optar por eliminar antes, el destructor de propietarios es el responsable final.