@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
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
El cuerpo de este constructor está vacío. En tanto que los constructores para base1
, member2
y member3
son una excepción segura, no hay nada de qué preocuparse. Por ejemplo, si el constructor de member2
tiros, ese constructor es responsable de limpiarse. La base base1
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.