web-dev-qa-db-fra.com

Quand est-il sûr d'appeler ceci-> dans le constructeur et le destructeur

Je n'ai pas été en mesure de trouver une réponse concluante à ce sujet jusqu'à présent. Quand est-il sûr d'appeler this-> depuis un objet. Et en particulier de l'intérieur du constructeur et du destructeur.

Et aussi, lors de l'utilisation de l'héritage public. Est-il sécuritaire d'utiliser et de downcasting sur le résultat de cet appel?

Ainsi, par exemple:

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
   {


   }

}

Et enfin le plus improbable

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

Lesquels des cas ci-dessus sont légaux?

Note: Je suis conscient que beaucoup des pratiques ci-dessus sont déconseillées.

32
laurisvr

Dans toute fonction membre non statique, this pointe vers l'objet sur lequel la fonction a été appelée. Il est sûr de l'utiliser tant qu'il s'agit d'un objet valide.

Dans le corps d'un constructeur ou d'un destructeur, il y a un objet valide de la classe en cours de construction. Cependant, s'il s'agit du sous-objet de base d'une classe dérivée, alors seul le sous-objet de base est valide à ce moment; il n'est donc généralement pas sûr de rétrograder et d'essayer d'accéder aux membres de la classe dérivée. Pour la même raison, vous devez être prudent lorsque vous appelez des fonctions virtuelles ici, car elles sont réparties en fonction de la classe en cours de création ou de destruction, et non du remplacement final.

Dans la liste d'initialisation d'un constructeur, vous devrez faire attention uniquement à accéder aux membres qui ont été initialisés; c'est-à-dire que les membres se sont déclarés avant celui en cours d'initialisation.

La conversion vers une classe de base est toujours sûre, car les sous-objets de base sont toujours initialisés en premier.

Pour les exemples spécifiques que vous venez d'ajouter à la question:

  • le cas 1 est correct (s'il est fragile), puisque a a été initialisé à ce stade. L'initialisation de a avec la valeur de b ne serait pas définie, puisque b est initialisé après a.
  • le cas 2 est très bien: tous les membres ont été initialisés à ce stade.
  • le cas 3 ne sera pas compilé, car il n'y a pas de constructeur foo approprié. S'il y en avait, cela dépendrait de ce que ce constructeur en ferait - s'il essayait ou non d'accéder aux membres avant leur initialisation.
  • le cas 4 serait bien formé si vous ajoutez le ) manquant, mais dangereux si vous essayez d'utiliser le pointeur pour accéder à l'objet. this ne pointe pas encore vers un objet bar valide (seule la partie foo a été initialisée), donc l'accès aux membres de bar pourrait donner un comportement indéfini. Le simple fait de vérifier si le pointeur n'est pas nul est correct et donnera toujours true (que vous appliquiez ou non un cast inutile).
34
Mike Seymour

Il y a une bonne entrée à ce sujet dans la super-faq C++:

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

Certaines personnes pensent que vous ne devez pas utiliser le pointeur this dans un constructeur car l'objet n'est pas encore complètement formé. Cependant, vous pouvez l'utiliser dans le constructeur (dans le {corps} et même dans la liste d'initialisation) si vous faites attention.

Voici quelque chose qui fonctionne toujours: le {corps} d'un constructeur (ou une fonction appelée depuis le constructeur) peut accéder de manière fiable aux membres de données déclarés dans une classe de base et/ou aux membres de données déclarés dans la propre classe du constructeur. Cela est dû au fait que tous ces membres de données sont garantis avoir été entièrement construits au moment où le {corps} du constructeur commence à s'exécuter.

Voici quelque chose qui ne fonctionne jamais: le {corps} d'un constructeur (ou une fonction appelée depuis le constructeur) ne peut pas accéder à une classe dérivée en appelant une fonction membre virtuelle qui est remplacée dans la classe dérivée. Si votre objectif était d'accéder à la fonction remplacée dans la classe dérivée, vous n'obtiendrez pas ce que vous voulez. Notez que vous n'obtiendrez pas la substitution dans la classe dérivée indépendamment de la façon dont vous appelez la fonction membre virtuelle: en utilisant explicitement le pointeur this (par exemple, this-> method ()), en utilisant implicitement le pointeur this (par exemple, method ( )), ou même appeler une autre fonction qui appelle la fonction membre virtuelle sur votre cet objet. Le résultat est le suivant: même si l'appelant construit un objet d'une classe dérivée, pendant le constructeur de la classe de base, votre objet n'est pas encore de cette classe dérivée. Tu étais prévenu.

Voici quelque chose qui fonctionne parfois: si vous transmettez l'un des membres de données de cet objet à l'initialiseur d'un autre membre de données, vous devez vous assurer que l'autre membre de données a déjà été initialisé. La bonne nouvelle est que vous pouvez déterminer si l'autre membre de données a (ou non) été initialisé à l'aide de règles de langage simples qui sont indépendantes du compilateur particulier que vous utilisez. La mauvaise nouvelle est que vous devez connaître ces règles de langage (par exemple, les sous-objets de la classe de base sont d'abord initialisés (recherchez l'ordre si vous avez un héritage multiple et/ou virtuel!), Puis les membres de données définis dans la classe sont initialisés dans l'ordre dans lequel ils apparaissent dans la déclaration de classe). Si vous ne connaissez pas ces règles, ne transmettez aucun membre de données de cet objet (que vous utilisiez ou non explicitement le mot-clé this) à l'initialiseur d'un autre membre de données! Et si vous connaissez les règles, soyez prudent.

19
Goz

Le pointeur this est accessible dans toutes les fonctions membres non statiques ...

§9.3.2/1

Dans le corps d'une fonction membre non statique (9.3), le mot clé this est une expression de valeur dont la valeur est l'adresse de l'objet pour lequel la fonction est appelée. Le type de ceci dans une fonction membre d'une classe X est X *. Si la fonction membre est déclarée const, le type de ceci est const X *, si la fonction membre est déclarée volatile, le type de ceci est volatile X *, et si la fonction membre est déclarée const volatile, le type de ceci est const volatile X *.

... où les constructeurs et les destructeurs sont des fonctions membres ...

§12/1

Le constructeur par défaut (12.1), le constructeur de copie et l'opérateur d'affectation de copie (12.8), le constructeur de déplacement et l'opérateur d'affectation de déplacement (12.8) et le destructeur (12.4) sont des fonctions membres spéciales.

... qui ne sont pas statiques.

§12.1/4

Un constructeur ne doit pas être virtuel (10.3) ou statique (9.4).

§12.4/2

Un destructeur ne doit pas être statique.

Ainsi, this est disponible dans les constructeurs et les destructeurs. Mais il y a des limitations (notamment en ce qui concerne l'utilisation de this dans la liste des initialiseurs).

(Remarque: à l'intérieur du corps constructeur/destructeur, l'initialisation de tous les sous-objets et membres est terminée et ils sont accessibles; voir ci-dessous ci-dessous).

1. Accès uniquement aux objets en cours de construction (ou à leurs sous-objets) via this.

§12.1/14

Lors de la construction d'un objet const, si la valeur de l'objet ou de l'un de ses sous-objets est accessible via une valeur gl qui n'est pas obtenue, directement ou indirectement, à partir du pointeur this du constructeur, la valeur de l'objet ou le sous-objet ainsi obtenu n'est pas spécifié.

2. N'appelez pas les fonctions virtuelles qui sont remplacées dans une classe dérivée dans le constructeur de base

§12.7/4

Les fonctions membres, y compris les fonctions virtuelles (10.3), peuvent être appelées pendant la construction ou la destruction (12.6.2). Lorsqu'une fonction virtuelle est appelée directement ou indirectement à partir d'un constructeur ou d'un destructeur, y compris pendant la construction ou la destruction des membres de données non statiques de la classe, et l'objet auquel l'appel s'applique est l'objet (appelez-le x) en construction ou destruction, la fonction appelée est l'outrider final dans la classe du constructeur ou du destructeur et non celle qui la remplace dans une classe plus dérivée. Si l'appel de fonction virtuelle utilise un accès explicite aux membres de classe (5.2.5) et que l'expression d'objet fait référence à l'objet complet de x ou à l'un des sous-objets de classe de base de cet objet mais pas à x ou à l'un de ses sous-objets de classe de base, le comportement n'est pas défini .

. Ne pas appliquer dynamic_cast pour convertir this en tout type autre que le type en construction ou tout type de base de celui-ci.

§12.7/6

dynamic_casts (5.2.7) peut être utilisé pendant la construction ou la destruction (12.6.2). Lorsqu'un dynamic_cast est utilisé dans un constructeur (y compris le mem-initializer ou brace-or-equal-initializer pour un membre de données non statique) ou dans un destructeur, ou utilisé dans une fonction appelée (directement ou indirectement) depuis un constructeur ou destructeur, si l'opérande de dynamic_cast fait référence à l'objet en cours de construction ou de destruction, cet objet est considéré comme un objet le plus dérivé ayant le type du constructeur ou de la classe du destructeur. Si l'opérande de dynamic_cast fait référence à l'objet en cours de construction ou de destruction et que le type statique de l'opérande n'est pas un pointeur ou un objet de la propre classe du constructeur ou du destructeur ou l'une de ses bases, le dynamic_cast entraîne un comportement indéfini.

4. La conversion de this en pointeur de type de base n'est autorisée que via des chemins composés de types de base construits.

§12.7/3

Pour convertir explicitement ou implicitement un pointeur (une glvalue) faisant référence à un objet de classe X en un pointeur (référence) en une base directe ou indirecte classe B de X, la construction de X et la construction de toutes ses bases directes ou indirectes qui dérivent directement ou indirectement de B doivent avoir commencé et la destruction de ces classes ne doit pas être terminée, sinon la conversion entraîne un comportement indéfini. Pour former un pointeur vers (ou accéder à la valeur de) un membre direct non statique d'un objet obj, la construction de obj doit avoir commencé et sa destruction ne doit pas être terminée, sinon le calcul de la valeur du pointeur (ou l'accès au membre ) entraîne un comportement indéfini.

Accès aux sous-objets et membres de la liste d'initialisation et du corps du constructeur

En principe, vous pouvez accéder aux objets construits/initialisés à partir de la liste d'initialisation, si leur initialisation a lieu avant d'y accéder. L'ordre d'initialisation est

§12.6.2/10

Dans un constructeur non délégué, l'initialisation se déroule dans l'ordre suivant:

  • Premièrement, et uniquement pour le constructeur de la classe la plus dérivée (1.8), les classes de base virtuelles sont initialisées dans l'ordre dans lequel elles apparaissent sur une profondeur de gauche à droite du graphe acyclique dirigé des classes de base, où "left- to-right "est l'ordre d'apparition des classes de base dans la liste dérivée des spécificateurs de base des classes.

  • Ensuite, les classes de base directes sont initialisées dans l'ordre de déclaration telles qu'elles apparaissent dans la liste des spécificateurs de base (quel que soit l'ordre des initialiseurs mem).

  • Ensuite, les membres de données non statiques sont initialisés dans l'ordre dans lequel ils ont été déclarés dans la définition de classe (là encore, quel que soit l'ordre des initialiseurs mem).

  • Enfin, l'instruction composée du corps du constructeur est exécutée.

7
Pixelchemist