simple programacion orientada objetos objeto herencia ejemplo destructores crear constructores como clases c++ c++11 constructor destructor

c++ - programacion - Cuando es seguro llamar a esto-> en constructor y destructor



herencia simple c++ (3)

El puntero de este es accesible en cada función miembro no estática ...

§9.3.2 / 1

En el cuerpo de una función miembro no estática (9.3), la palabra clave es una expresión prvalue cuyo valor es la dirección del objeto para el que se llama la función. El tipo de esto en una función miembro de una clase X es X *. Si la función miembro se declara const, el tipo de esto es const X *, si la función miembro se declara volátil, el tipo de este es volátil X * y la función miembro se declara const volatile, el tipo de esto es const. X volátil *.

... donde constructores y destructores son funciones miembro ...

§12 / 1

El constructor predeterminado (12.1), el constructor de copia y el operador de asignación de copia (12.8), el constructor de movimiento y el operador de asignación de movimiento (12.8), y el destructor (12.4) son funciones miembro especiales.

... que no son estáticos.

§12.1 / 4

Un constructor no será virtual (10.3) ni estático (9.4).

§12.4 / 2

Un destructor no será estático.

Así, this está disponible en constructores y destructores. Pero hay limitaciones (especialmente con respecto al uso de this dentro de la lista de inicializadores).

(Nota: Dentro del cuerpo del constructor / destructor, la inicialización de todos los subobjetos y miembros se completa y son accesibles; vea más abajo).

1. Solo acceda al objeto que se está construyendo (o sus subobjetos) a través de this .

§12.1 / 14

Durante la construcción de un objeto const, si se accede al valor del objeto o cualquiera de sus subobjetos a través de un valor de gl, que no se obtiene, directa o indirectamente, del puntero de this constructor, el valor del objeto o subobjeto así obtenido no se especifica. .

2. No llamar a funciones virtuales que se anulan en una clase derivada en el constructor base

§12.7 / 4

Las funciones de los miembros, incluidas las funciones virtuales (10.3), se pueden llamar durante la construcción o la destrucción (12.6.2). Cuando una función virtual se llama directa o indirectamente desde un constructor o desde un destructor, incluso durante la construcción o destrucción de los miembros de datos no estáticos de la clase, y el objeto al que se aplica la llamada es el objeto (llámelo x) en construcción o destrucción, la función llamada es el anulador final en la clase del constructor o destructor y no una anulación en una clase más derivada. Si la llamada a la función virtual utiliza un acceso explícito a un miembro de la clase (5.2.5) y la expresión del objeto se refiere al objeto completo de x o uno de los subobjetos de la clase base de ese objeto, pero no a x o uno de sus subobjetos de la clase base, el comportamiento no está definido .

3. No aplique dynamic_cast para convertir this en ningún otro tipo que no sea el tipo en construcción o cualquier tipo de base del mismo.

§12.7 / 6

Dynamic_casts (5.2.7) se puede usar durante la construcción o destrucción (12.6.2). Cuando se utiliza un dynamic_cast en un constructor (incluido el inicializador de mem o el iniciador de brace o igual para un miembro de datos no estáticos) o en un destructor, o se usa en una función llamada (directa o indirectamente) de un constructor o destructor, si el operando de dynamic_cast se refiere al objeto en construcción o destrucción, este objeto se considera como el objeto más derivado que tiene el tipo de constructor o clase de destructor. Si el operando de dynamic_cast se refiere al objeto en construcción o destrucción y el tipo estático del operando no es un puntero u objeto de la clase propia del constructor o destructor o una de sus bases, el dynamic_cast produce un comportamiento indefinido.

4. La conversión de this puntero a tipo base solo se permite a través de rutas que consisten en tipos base construidos.

§12.7 / 3

Para convertir explícita o implícitamente un puntero (un glvalor) que se refiere a un objeto de la clase X en un puntero (referencia) a una clase de base B directa o indirecta de X, la construcción de X y la construcción de todas sus bases directas o indirectas que directa o indirectamente se derive de B habrá comenzado y la destrucción de estas clases no se habrá completado, de lo contrario la conversión resultará en un comportamiento indefinido. Para formar un puntero a (o acceder al valor de) un miembro directo no estático de un objeto obj, la construcción de obj deberá haber comenzado y su destrucción no se habrá completado; de lo contrario, el cálculo del valor del puntero (o el acceso al miembro). valor) resulta en un comportamiento indefinido.

Acceso a subobjetos y miembros en la lista de inicializadores y el cuerpo del constructor

En principio, puede acceder a los objetos construidos / inicializados desde la lista de inicializadores, si su inicialización tiene lugar antes de acceder a ellos. El orden de inicialización es

§12.6.2 / 10

En un constructor no delegante, la inicialización se realiza en el siguiente orden:

  • Primero, y solo para el constructor de la clase más derivada (1.8), las clases de base virtual se inicializan en el orden en que aparecen en un recorrido transversal de izquierda a derecha, primero en profundidad, del gráfico acíclico dirigido de las clases de base, donde a-derecha ”es el orden de aparición de las clases base en la lista derivada-especificador-base de la clase.

  • Luego, las clases básicas directas se inicializan en el orden de declaración tal como aparecen en la lista de especificadores de base (independientemente del orden de los inicializadores de memoria).

  • Luego, los miembros de datos no estáticos se inicializan en el orden en que fueron declarados en la definición de la clase (de nuevo, independientemente del orden de los inicializadores de memoria).

  • Finalmente, se ejecuta la declaración compuesta del cuerpo del constructor.

No he podido encontrar una respuesta concluyente a esto hasta ahora. ¿Cuándo es seguro llamar a this-> desde dentro de un objeto? Y en particular desde el interior del constructor y destructor.

Y también, al usar la herencia pública. ¿Es seguro usar hacia arriba y hacia abajo en el resultado de esta llamada?

Así por ejemplo:

class foo { foo(): a(), b(this->a)//case 1 { this-> a = 5; //case 2 } int a; int b; }; class bar: public baz { bar(): baz(this)//case 3 - assuming baz has a valid constructor { } }

Y por fin el más improbable.

foo() { if(static_cast<bar*>(this));//case 4 }

¿Cuáles de los casos anteriores son legales?

Nota: soy consciente de que muchas de las prácticas anteriores no son recomendables.


Dentro de cualquier función miembro no estática, this apunta al objeto sobre el que se llamó la función. Es seguro utilizarlo siempre que sea un objeto válido.

Dentro del cuerpo de un constructor o destructor, hay un objeto válido de la clase que se está construyendo actualmente. Sin embargo, si este es el subobjeto base de alguna clase derivada, entonces solo el subobjeto base es válido en ese momento; por lo tanto, generalmente no es seguro descender y tratar de acceder a los miembros de la clase derivada. Por la misma razón, debe tener cuidado al llamar a las funciones virtuales aquí, ya que se envían de acuerdo con la clase que se está creando o destruyendo, no el anulador final.

Dentro de la lista de inicializadores de un constructor, deberá tener cuidado solo para acceder a los miembros que se han inicializado; es decir, miembros declarados antes del inicializado actualmente.

La conversión ascendente a una clase base siempre es segura, ya que los subobjetos base siempre se inicializan primero.

Para los ejemplos específicos que acaba de agregar a la pregunta:

  • el caso 1 está bien (si es frágil), ya que a se ha inicializado en ese punto. Inicializar a con el valor de b sería indefinido, ya que b se inicializa después de a .
  • el caso 2 está bien: todos los miembros se han inicializado en ese momento.
  • el caso 3 no se compilará, ya que no hay un constructor de foo adecuado. Si lo hubiera, entonces dependería de lo que el constructor hizo con él, ya sea que intentara o no acceder a los miembros antes de que fueran inicializados.
  • el caso 4 estaría bien formado si añadía el que faltaba ) , pero peligroso si intentara usar el puntero para acceder al objeto. this aún no apunta a un objeto de bar válido (solo se ha inicializado la parte de foo ), por lo que el acceso a los miembros de la bar podría dar un comportamiento indefinido. Simplemente comprobar si el puntero no es nulo está bien, y siempre dará true (ya sea que aplique o no un lanzamiento sin sentido).

Hay una buena entrada en la super-faq de C ++:

https://isocpp.org/wiki/faq/ctors#using-this-in-ctors

Algunas personas sienten que no debes usar este puntero en un constructor porque el objeto todavía no está completamente formado. Sin embargo, puede usar esto en el constructor (en el {cuerpo} e incluso en la lista de inicialización) si tiene cuidado.

Aquí hay algo que siempre funciona: el {cuerpo} de un constructor (o una función llamada del constructor) puede acceder de manera confiable a los miembros de datos declarados en una clase base y / o a los miembros de datos declarados en la propia clase del constructor. Esto se debe a que se garantiza que todos esos miembros de datos se han construido completamente en el momento en que el {cuerpo} del constructor comienza a ejecutarse.

Aquí hay algo que nunca funciona: el {cuerpo} de un constructor (o una función llamada del constructor) no puede pasar a una clase derivada llamando a una función miembro virtual que está anulada en la clase derivada. Si su objetivo era llegar a la función anulada en la clase derivada, no obtendrá lo que desea. Tenga en cuenta que no obtendrá la anulación en la clase derivada, independientemente de cómo llame a la función miembro virtual: usando explícitamente el puntero this (por ejemplo, this-> method ()), usando implícitamente el puntero this (eg, method ( )), o incluso llamando a alguna otra función que llame a la función miembro virtual en este objeto. La conclusión es esta: incluso si el llamante está construyendo un objeto de una clase derivada, durante el constructor de la clase base, su objeto aún no es de esa clase derivada. Usted ha sido advertido.

Aquí hay algo que a veces funciona: si pasa alguno de los miembros de datos en este objeto al inicializador de otro miembro de datos, debe asegurarse de que el otro miembro de datos ya se haya inicializado. La buena noticia es que puede determinar si el otro miembro de datos se ha inicializado (o no) utilizando algunas reglas de lenguaje sencillas que son independientes del compilador particular que está utilizando. La mala noticia es que debe conocer esas reglas de idioma (por ejemplo, los subobjetos de la clase base se inicializan primero (busque el orden si tiene herencia múltiple y / o virtual), luego los miembros de datos definidos en la clase se inicializan en el orden en que aparecen en la declaración de clase). Si no conoce estas reglas, no pase ningún miembro de datos de este objeto (independientemente de si usa o no explícitamente esta palabra clave) al inicializador de cualquier otro miembro de datos. Y si conoces las reglas, por favor ten cuidado.