¿Qué (no) hacer en un constructor


Quiero pedirle sus mejores prácticas con respecto a los constructores en C++. No estoy muy seguro de lo que debería hacer en un constructor y lo que no.

Debería usarlo solo para inicializaciones de atributos, llamando a constructores padre, etc.? O podría incluso poner funciones más complejas en ellos como la lectura y análisis de datos de configuración, la creación de bibliotecas externas a.s.o.

O debo escribir funciones especiales para esto? Resp. init() / cleanup()?

¿Cuáles son los PRO y CON está aquí?

Me di cuenta todavía que, por ejemplo, puedo deshacerme de los punteros compartidos cuando uso init() y cleanup(). Puedo crear los objetos en la pila como atributos de clase e inicializarlos más tarde mientras ya está construido.

Si lo manejo en el constructor necesito instanciarlo durante el tiempo de ejecución. Entonces necesito un indicador.

Realmente no sé cómo decidir.

¿Tal vez puedas ayudarme?

Author: deft_code, 2010-10-11

12 answers

La lógica compleja y el constructor no siempre se mezclan bien, y hay fuertes defensores en contra de hacer trabajo pesado en un constructor (con razones).

La regla cardinal es que el constructor debe producir un objeto completamente utilizable.

class Vector
{
public:
  Vector(): mSize(10), mData(new int[mSize]) {}
private:
  size_t mSize;
  int mData[];
};

No significa un objeto completamente inicializado, puede diferir alguna inicialización (piense perezoso) siempre y cuando el usuario no tenga que pensar en ello.

class Vector
{
public:
  Vector(): mSize(0), mData(0) {}

  // first call to access element should grab memory

private:
  size_t mSize;
  int mData[];
};

Si hay trabajo pesado que hacer, puede optar por proceder con un constructor método, que hará el trabajo pesado antes de llamar al constructor. Por ejemplo, imagine recuperar la configuración de una base de datos y crear un objeto de configuración.

// in the constructor
Setting::Setting()
{
  // connect
  // retrieve settings
  // close connection (wait, you used RAII right ?)
  // initialize object
}

// Builder method
Setting Setting::Build()
{
  // connect
  // retrieve settings

  Setting setting;
  // initialize object
  return setting;
}

Este método de constructor es útil si posponer la construcción del objeto produce un beneficio significativo. Por ejemplo, si los objetos capturan mucha memoria, posponer la adquisición de memoria después de las tareas que probablemente fallen no puede ser una mala idea.

Este método de constructor implica constructor privado y Público (o amigo) Constructor. Tenga en cuenta que tener un constructor privado impone una serie de restricciones sobre los usos que se pueden hacer de una clase (no se puede almacenar en contenedores STL, por ejemplo), por lo que es posible que deba combinar otros patrones. Por esta razón, este método solo debe utilizarse en circunstancias excepcionales.

Es posible que desee considerar cómo probar tales entidades también, si depende de una cosa externa (archivo / DB), piense en la inyección de dependencias, realmente ayuda con las Pruebas Unitarias.

 22
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 11:53:33

El error más común para hacer tanto en un constructor como en un destructor, es usar polimorfismo. El polimorfismo a menudo no funciona en constructores !

Ej:

class A
{
public:
    A(){ doA();} 
    virtual void doA(){};
}

class B : public A
{
public:
    virtual void doA(){ doB();};
    void doB(){};   
}


void testB()
{
    B b; // this WON'T call doB();
}

Esto se debe a que el objeto B aún no está construido mientras realiza el constructor de la clase madre A... por lo tanto, es imposible que llame a la versión anulada de void doA();

Ben, en los comentarios a continuación, me ha pedido un ejemplo donde el polimorfismo funcionará en constructor.

Ej:

class A
{
public: 
    void callAPolymorphicBehaviour()
    {
        doOverridenBehaviour(); 
    }

    virtual void doOverridenBehaviour()
    {
        doA();
    }

    void doA(){}
};

class B : public A
{
public:
    B()
    {
        callAPolymorphicBehaviour();
    }

    virtual void doOverridenBehaviour()
    {
        doB()
    }

    void doB(){}
};

void testB()
{
   B b; // this WILL call doB();
}

Esta vez, la razón detrás es: en el momento en que se invoca la función virtual doOverridenBehaviour(), el objeto b ya está inicializado (pero aún no construido), esto significa que su tabla virtual está inicializada, y por lo tanto puede realizar polimorfismo.

 27
Author: Stephane Rolland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-18 15:43:16
  • No llame a delete this o al destructor en el constructor.
  • No use miembros init()/cleanup (). Si tienes que llamar a init() cada vez que creas una instancia, todo en init () debería estar en el constructor. El constructor está destinado a poner la instancia en un estado consistente que permite llamar a cualquier miembro público con un comportamiento bien definido. De manera similar para cleanup(), además de cleanup() mata RAII. (Sin embargo, cuando tienes varios constructores, a menudo es útil para tener una función privada init () que es llamada por el them.)
  • Hacer cosas más complejas en constructores está bien, dependiendo del uso previsto de las clases y su diseño general. Por ejemplo, no sería una buena idea leer un archivo en el constructor de algún tipo de Entero o clase de Punto; los usuarios esperan que sea barato crearlos. También es importante considerar cómo los constructores de acceso a archivos afectarán su capacidad para escribir pruebas unitarias. La mejor solución suele ser tener un constructor que solo toma los datos que necesita para construir los miembros y escribe una función no miembro que hace el análisis del archivo y devuelve una instancia.
 15
Author: Steve M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 11:47:50

Respuesta simple: depende.

Al diseñar su software, es posible que desee programar a través del principio RAII ("La adquisición de recursos es la inicialización"). Esto significa (entre otras cosas) que el objeto en sí es responsable de sus recursos, y no el que llama. Además, es posible que desee familiarizarse con seguridad de excepción (en sus diferentes grados).

Por ejemplo, considere:

void func() {
    MyFile f("myfile.dat");
    doSomething(f);
}

Si diseña la clase MyFile en el camino, que usted puede estar seguro antes de doSomething(f) que f se inicializa, se ahorra un montón de problemas para comprobar que. Además, si sueltas los recursos mantenidos por f en el destructor, es decir, cierras el controlador de archivo, estás en el lado seguro y es fácil de usar.

En este caso específico puede usar las propiedades especiales de los constructores:

  • Si lanzas una excepción del constructor a su mundo exterior, el objeto no se creará. Esto significa, la destructor no será llamado y la memoria será liberada inmediatamente.
  • Un constructor debe ser llamado. No se puede obligar al usuario a utilizar ninguna otra función (excepto el destructor), solo por convención. Por lo tanto, si desea forzar al usuario a inicializar su objeto, ¿por qué no a través del constructor?
  • Si tiene algún método virtual, no debe llamar a esos desde el interior del constructor, a menos que sepa lo que está haciendo -- me sorprende por qué no se llama al método virtual overriding. Mejor no confundir a nadie.

Un constructor debe dejar su objeto en un estado usable. Y debido a que siempre es sabio hacer que sea difícil usar su API mal , lo mejor que puede hacer es hacer que sea fácil de usar correctamente (sic a Scott Meyers). Hacer inicialización dentro del constructor debe ser su estrategia predeterminada but pero siempre hay excepciones, de curso.

So: Es una buena manera de usar constructores para la inicialización. No siempre es posible, por ejemplo, los frameworks GUI a menudo necesitan ser construidos, luego inicializados. Pero si diseña su software completamente en esta dirección, puede ahorrarse muchos problemas.

 9
Author: towi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 13:07:27

De El Lenguaje de Programación C++ :

El uso de funciones como init() para proporcionar inicialización para objetos de clase es poco elegante y errorrone. Debido a que en ninguna parte se indica que un objeto debe ser inicializado, un programador puede olvidarse de hacerlo – o hacerlo dos veces (a menudo con resultados igualmente desastrosos). Un mejor enfoque es permitir al programador declarar una función con el propósito explícito de inicializar objetos. Porque tal función construye valores de un tipo dado, se llama constructor.

Normalmente considero la siguiente regla al diseñar una clase: Debo ser capaz de usar cualquier método de la clase de forma segura después de que el constructor se haya ejecutado. Safe here significa que siempre puedes lanzar excepciones si el método init() del objeto no ha sido llamado, pero prefiero tener algo que sea realmente utilizable.

Por ejemplo, class std::string podría no asignar ninguna memoria cuando se utiliza el constructor predeterminado porque la mayoría de los métodos (es decir, begin() y end()) funcionarían correctamente si ambos devuelven punteros nulos, y c_str() no necesariamente devuelve el búfer actual por otras razones de diseño, por lo tanto tiene que estar preparado para asignar memoria en cualquier momento. No asignar memoria en este caso todavía conduce a una instancia de cadena perfectamente utilizable.

Por el contrario, el uso de RAII en guardas de ámbito para bloqueos mutex es un ejemplo de un constructor que puede ejecutarse durante un tiempo arbitrariamente largo (hasta que el propietario de la cerradura lo libera) sin embargo, todavía es comúnmente aceptado como una buena práctica.

En cualquier caso, la inicialización perezosa se puede hacer de maneras más seguras que usando un método init(). Una forma es usar alguna clase intermedia que capture todos los parámetros del constructor. Otra es utilizar el patrón constructor.

 6
Author: André Caron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-14 10:35:30

Se espera que un constructor cree un objeto que se pueda usar desde el principio. Si por alguna razón no es capaz de crear un objeto utilizable, debe lanzar una excepción y terminar con él. Por lo tanto, todos los métodos/funciones suplementarios que son necesarios para que un objeto funcione correctamente deben ser llamados desde el constructor (a menos que desee tener características similares a lazy-loading)

 4
Author: blahster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 11:44:40

Prefiero preguntar:

What all to do in the constructor?

Y todo lo que no está cubierto anteriormente es respuesta a la pregunta del OP.

Creo que el único propósito del constructor es

  1. Inicializar todas las variables miembro a un estado conocido, y

  2. Asignar recursos (si corresponde).

El elemento #1 suena tan simple, sin embargo, veo que ser olvidado/ignorado sobre una base regular y solo para ser recordado por una herramienta de análisis estático. Nunca subestime esto (juego de palabras).

 4
Author: Arun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 12:01:43

Puede lanzar desde un constructor, y a menudo es la mejor opción que crear un objeto zombie, es decir, un objeto que tiene un estado "fallido".

Sin embargo, nunca debes lanzar desde un destructor.

El compilador sabrá qué orden están construidos los objetos miembro - el orden en que aparecen en la cabecera. Sin embargo, el destructor no se llamará como dijiste, lo que significa que si estás llamando a new varias veces dentro de un constructor no puedes confiar en tu destructor llamando a las eliminaciones por ti. Si los coloca en objetos de puntero inteligente, no es un problema, ya que estos objetos se eliminarán. Si los quieres como punteros raw entonces colócalos temporalmente en objetos auto_ptr hasta que sepas que tu constructor ya no lanzará, entonces llama release() en todos tus auto_ptrs.

 4
Author: CashCow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 12:04:33

Un constructor se utiliza para construir un objeto - nada más y nada menos. Es necesario hacer lo que sea necesario para establecer las invariantes de clase dentro de un constructor y lo complejo que es realmente depende del tipo del objeto que se inicializa.

Separar las funciones init() es una buena idea solo si por alguna razón no puedes usar excepciones.

 3
Author: Nemanja Trifunovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 13:48:10

Bueno, "constructor" viene de la construcción, la construcción, la configuración. Así que ahí es donde ocurre toda la inicialización. Cada vez que instales una clase, usa el constructor para asegurarte de que todo está hecho para hacer que el nuevo objeto sea viable.

 2
Author: Júlio Santos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 11:35:15

Puedes hacer lo que quieras, pero usa constructor para ese propósito para lo que es call - create object. y si para que la necesidad de llamar a otros métodos, que está bien. solo sigue una regla: no lo compliques más de lo necesario. Una buena práctica es hacer constructor lo más simple posible, pero eso no significa que solo necesite inicializar miembros.

 1
Author: Dainius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 12:13:23

Creo que lo más importante es un poco de sentido común! Se habla mucho de lo que se debe hacer y lo que no se debe hacer, todo muy bien, pero el punto clave a considerar es cómo se usará tu objeto. Por ejemplo,

  1. ¿cuántas instancias de este objeto se crearán? (¿ se guardan en contenedores, por ejemplo?)
  2. ¿con qué frecuencia son creados y destruidos? (bucles?)
  3. qué tan grande es?

Si este objeto es una sola instancia, que se construye al inicio y el la construcción no está en el camino crítico - ¿por qué no hacer el trabajo pesado en el constructor (siempre y cuando utilice las excepciones adecuadamente, incluso puede tener más sentido)? Si, por otro lado, es un objeto muy ligero que se crea y destruye en un bucle durante un corto período de tiempo, intente hacer lo menos posible (aparte de inicializar los miembros, por ejemplo) (los funtores son un muy buen ejemplo de esto...)

Hay ventajas de tener una carga de dos fases( o lo que sea), pero principal la desventaja es olvidarse de llamarlo - ¿cuántos de nosotros hemos hecho eso?? :)

Así que, mis dos peniques es, no te apegues a una regla dura y rápida, mira cuidadosamente cómo se va a usar tu objeto, y luego diseñarlo para que se adapte.

 1
Author: Nim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-11 12:58:26