En programación orientada a objetos (POO), un constructor es una subrutina cuya misión es inicializar un objeto de una clase. Es decir, el constructor nos va a permitir crear una instancia de una clase (un objeto). Un constructor es una función especial miembro de una clase que se invoca automáticamente cuando se crea un objeto de esa clase.
Para crear un objeto se necesita reservar suficiente espacio en memoria e inicializar los valores de los campos que representan el estado del objeto. El principal propósito del constructor es inicializar los miembros de la clase y realizar cualquier configuración necesaria para asegurar que los objetos comiencen en un estado válido. Recordemos que los objetos se crean con ayuda de un constructor, el cual es un método especial que reserva la memoria para todos los atributos del objeto además de darnos la opción de inicializarlos.
Sintaxis y Declaración de Constructores
Un método constructor de una clase es un método especial que tiene el mismo nombre que la clase y no tiene tipo de retorno, ni siquiera void. La sintaxis básica de un constructor es simplemente el propio nombre de la clase. Normalmente, los constructores tienen accesibilidad pública para que el código pueda crear objetos de la clase. En este curso siempre vamos a utilizar el modificador de acceso public para los constructores.
Lista de Inicialización de Miembros
Los constructores permiten inicializar miembros de la clase utilizando una lista de inicialización. Opcionalmente, un constructor puede tener una lista de inicializadores de miembros, que inicializa los miembros de la clase antes de que se ejecute el cuerpo del constructor. Son preferibles las listas de inicializadores de miembros antes que la asignación de valores en el cuerpo del constructor, ya que una lista de inicializadores de miembros inicializa directamente los miembros. Esto es preferible a la inicialización dentro del cuerpo del constructor, especialmente para miembros constantes o referencias.
Tipos de Constructores
Podemos escribir más de un constructor y así tener la posibilidad de construir objetos de distintas maneras, utilizando la sobrecarga de métodos. La sobrecarga de constructores significa que podemos tener múltiples constructores con diferentes firmas (número de parámetros y/o tipos de parámetros) y el compilador seleccionará el constructor adecuado en función de los argumentos que se pasen al crear un objeto. La sobrecarga de constructores funciona de manera similar a la sobrecarga de funciones.
Lea también: Contabilidad en la Construcción
A continuación, se describen los tipos de constructores más comunes:
- Constructor por Defecto (sin argumentos): Este constructor no permite datos como argumentos, sin embargo, puede inicializar atributos. En este caso, tenemos un constructor sin ningún parámetro. Toda clase tiene que tener un constructor. Si no definimos uno, el compilador proporcionará uno automáticamente (sin parámetros ni acciones).
- Constructor con Parámetros: Este constructor nos otorga la posibilidad de dar valores para inicializar el objeto al ser creado; los argumentos pueden ser de cualquier tipo de dato. Podemos tener constructores con uno o más argumentos, que nos permite inicializar un objeto con valores proporcionados como argumentos.
- Constructor de Copia: Se utiliza para crear un nuevo objeto como una copia de un objeto existente. Un constructor de copia inicializa un objeto copiando los valores de los miembros de un objeto del mismo tipo. Si los miembros de la clase son todos tipos simples, como valores escalares, el constructor de copia generado por el compilador es suficiente y no es necesario definir el suyo propio. Si la clase requiere una inicialización más compleja, debe implementar un constructor de copia personalizado. Al definir un constructor de copia, también debe definir un operador de asignación de copia (=).
- Constructor de Movimiento: Introducido en C++11, se utiliza para transferir recursos de un objeto a otro en lugar de copiarlos (para mejorar el rendimiento). Un constructor de movimiento es una función miembro especial que mueve la propiedad de los datos de un objeto existente a una nueva variable sin copiar los datos originales. El compilador elige un constructor de movimiento cuando otro objeto del mismo tipo inicializa el objeto, si el otro objeto está a punto de ser destruido y ya no necesita sus recursos.
- Constructor de Delegación: Un constructor de delegación llama a otro constructor de la misma clase para realizar algunas de las tareas de inicialización. Esta característica es útil cuando tiene varios constructores que todos tienen que realizar un trabajo similar.
Generación Automática de Constructores por el Compilador
Una cualidad interesante de C++, o mejor dicho, de los compiladores de C++ es la creación automática de funciones miembro en las clases, entre ellas el constructor. Cuando olvidamos escribir un constructor, el compilador nos proporciona uno por defecto, por eso es que una clase sin constructor puede ser compilada sin problemas. Sin embargo, si defines un constructor con parámetros, el constructor por defecto no se generará automáticamente.
Creación de Objetos (Instanciación)
Con nuestra clase ya escrita podemos crear cuantos objetos queramos simplemente instanciando objetos de esta clase. Lo que acabo de hacer es instanciar una clase, es decir, crear un objeto de esa clase en la variable.
Existen dos maneras principales de crear objetos:
- Objetos temporales: Esta manera nos otorga un objeto que podremos usar de manera muy similar a cualquier tipo de dato primitivo del lenguaje, por ejemplo un entero int. Un objeto creado en el ámbito de una función existirá mientras la ejecución del programa no deje el bloque de dicha función; al salir de ella la memoria es liberada automáticamente.
- Apuntadores a objeto: Esta segunda manera proporciona la memoria necesaria para el objeto y nos otorga la dirección en un apuntador. Un apuntador a objeto es aquel al cual se le ha asignado la dirección de memoria para manipular un objeto de la clase.
Uso de la palabra clave `this`
Cuando se trabaja con constructores con parámetros que tienen el mismo nombre que los atributos de la clase, se hace uso de la palabra reservada this. Por ejemplo, al escribir this.marca nos estamos refiriendo a la variable marca de la clase y no al parámetro marca. Es decir, cuando se escribe this.marca = marca; se está diciendo que la variable marca de la clase pase a valer lo que valga el parámetro marca.
Lea también: proyectos y servicios de CHN Constructores en Culiacán
Destructores en C++
Hasta ahora vimos cómo construir objetos, sin embargo, también se destruyen y para esto se usan los destructores, que son los métodos contrarios a los constructores y que se encargan justamente de lo opuesto: destruir objetos y liberar la memoria que ocupan. Un destructor es una función especial miembro de una clase que se invoca automáticamente cuando un objeto sale del ámbito (es decir, cuando se “destruye”).
A diferencia de los constructores, los destructores no aceptan parámetros ni devuelven valores. Su propósito principal es liberar cualquier recurso que el objeto pueda haber adquirido durante su vida. El nombre de un destructor es el nombre de la clase precedido por un tilde (~). Una clase solamente puede tener un destructor.
Manejo de Memoria y Destructores
La liberación de la memoria depende de nosotros; así como la hemos pedido con new, debemos liberarla usando delete. Si no lo hacemos es posible que esa memoria quede ocupada sin poder ser reasignada, al menos hasta que el programa deje de ejecutarse. En nuestra clase de ejemplo no tenemos un destructor ya que el que proporciona el compilador nos sirve bien y es que todos los atributos son destruidos de manera correcta y no requieren un tratamiento especial de liberación de memoria (como el caso de los apuntadores).
La mayoría de las veces estos importantes métodos son olvidados y llegan a faltar en las clases. Por suerte, los compiladores crean también destructores por defecto y es por eso que los programas compilan y parecen funcionar bien aun cuando prescindimos de ellos. Sin embargo, tal como sucede con los constructores, es aconsejable escribir uno mismo los destructores para asegurarse de que todo finaliza correctamente. A veces una clase mantiene un recurso que no es de Java como un descriptor de archivo o un tipo de letra del sistema de ventanas. En este caso sería acertado el utilizar la finalización explícita, para asegurar que dicho recurso se libera.
Lea también: Dominando los sustantivos contables e incontables en inglés
