Est-il sûr d'appeler placement nouveau sur «ceci» pour un objet trivial?

20

Je sais que cette question a déjà été posée plusieurs fois mais je n'ai pas trouvé de réponse pour ce cas particulier.

Disons que j'ai une classe triviale qui ne possède aucune ressource et qui a un destructeur vide et un constructeur par défaut. Il a une poignée de variables membres avec initialisation en classe; aucun d'entre eux ne l'est const.

Je veux réinitialiser et objecter une telle classe sans écrire de deInitméthode à la main. Est-il sûr de le faire comme ça?

void A::deInit()
{
  new (this)A{};
}

Je ne vois aucun problème avec lui - l'objet est toujours dans un état valide, thispointe toujours vers la même adresse; mais c'est C ++ donc je veux être sûr.

Amomum
la source
2
Y a-t-il des membres de l'objet const?
NathanOliver
2
Si cela est valable, serait-ce équivalent à *this = A{};?
Kevin
2
@Amomum *this = A{};signifie, this->operator=(A{});c'est-à-dire créer un objet temporaire et l'affecter à *this, en remplaçant les valeurs de tous les membres de données par les valeurs du temporaire. Puisque c'est ce que vous voulez et est (à mon avis) plus lisible qu'un nouveau placement, j'irais avec cela à la place.
Kevin
1
@Kevin oh, mon mauvais, tu as raison. Que - je pense que cela devrait être égal si la copie est élidée?
Amomum
1
au lieu d'expliquer la classe avec des mots, il est préférable d'écrire toute la classe, et pas seulement une méthode. voir sscce.org
BЈовић

Réponses:

17

De la même manière que pour la légalité delete this, le placement nouveau thisest également autorisé à ma connaissance. En outre, concernant l' thisutilisation éventuelle d'autres pointeurs / références préexistants, il existe quelques restrictions:

[basic.life]

Si, après la fin de la durée de vie d'un objet et avant que le stockage occupé par l'objet ne soit réutilisé ou libéré, un nouvel objet est créé à l'emplacement de stockage qu'occupait l'objet d'origine, un pointeur qui pointait vers l'objet d'origine, une référence qui fait référence à l'objet d'origine, ou le nom de l'objet d'origine fera automatiquement référence au nouvel objet et, une fois la durée de vie du nouvel objet commencée, pourra être utilisé pour manipuler le nouvel objet, si:

  • le stockage du nouvel objet recouvre exactement l'emplacement de stockage qu'occupait l'objet d'origine, et
  • le nouvel objet est du même type que l'objet d'origine (en ignorant les qualificatifs cv de niveau supérieur), et
  • le type de l'objet d'origine n'est pas qualifié par const et, s'il s'agit d'un type de classe, ne contient aucun membre de données non statique dont le type est qualifié par const ou un type de référence, et
  • ni l'objet d'origine ni le nouvel objet n'est un sous-objet potentiellement chevauchant ([intro.object]).

Les deux premiers sont satisfaits dans cet exemple, mais les deux derniers devront être pris en considération.

En ce qui concerne le troisième point, étant donné que la fonction n'est pas qualifiée const, il devrait être assez sûr de supposer que l'objet d'origine est non const. La faute est du côté de l'appelant si la constance a été rejetée. En ce qui concerne const / reference member, je pense que cela peut être vérifié en affirmant que c'est assignable:

static_assert(std::is_trivial_v<A> && std::is_copy_assignable_v<A>);

Bien sûr, comme l'attribution est une exigence, vous pouvez simplement utiliser *this = {};ce à quoi je m'attendrais pour produire le même programme. Un cas d'utilisation peut-être plus intéressant pourrait être de réutiliser la mémoire d' *thisun objet d'un autre type (ce qui ne répondrait pas aux exigences d'utilisation this, au moins sans réinterprétation + blanchiment).

De même delete this, un placement nouveau thispourrait difficilement être décrit comme «sûr».

eerorika
la source
Intéressant. Est-il possible de s'assurer que toutes ces conditions sont satisfaites avec static_assert sur certains traits de type? Je ne sais pas s'il y en a un sur les membres const ...
Amomum
1
@Amomum Je ne pense pas qu'être un sous-objet puisse être testé. Les membres const ou reference rendraient la classe non assignable, ce qui, je pense, ne peut pas se produire autrement pour une classe triviale.
eerorika
cela signifie-t-il que l'aliasing strict entre en jeu en ce qui concerne la constance? pouvez-vous donner un exemple où cela pourrait entrer en jeu?
darune
1
@darune Créez un objet const, placement-new dessus, utilisez le nom d'origine de l'objet (ou un pointeur ou référence préexistant) et le comportement ne sera pas défini. À peu près la même chose que l'optimisation stricte de l'alias, sinon la même chose.
eerorika
1
L'inverse de delete ptrest new T(). L'inverse de new(ptr)T{}est ptr->~T();. stackoverflow.com/a/8918942/845092
Mooing Duck
7

Les règles qui couvrent cela se trouvent dans [basic.life] / 5

Un programme peut mettre fin à la durée de vie de n'importe quel objet en réutilisant le stockage que l'objet occupe ou en appelant explicitement le destructeur pour un objet d'un type de classe. Pour un objet d'un type de classe, le programme n'est pas obligé d'appeler le destructeur explicitement avant que le stockage que l'objet occupe soit réutilisé ou libéré; cependant, s'il n'y a pas d'appel explicite au destructeur ou si une expression de suppression n'est pas utilisée pour libérer le stockage, le destructeur n'est pas implicitement appelé et tout programme qui dépend des effets secondaires produits par le destructeur a un comportement indéfini.

et [basic.life] / 8

Si, après la fin de la durée de vie d'un objet et avant que le stockage occupé par l'objet ne soit réutilisé ou libéré, un nouvel objet est créé à l'emplacement de stockage qu'occupait l'objet d'origine, un pointeur qui pointait vers l'objet d'origine, une référence qui fait référence à l'objet d'origine, ou le nom de l'objet d'origine fera automatiquement référence au nouvel objet et, une fois la durée de vie du nouvel objet commencée, pourra être utilisé pour manipuler le nouvel objet, si:

  • le stockage du nouvel objet recouvre exactement l'emplacement de stockage qu'occupait l'objet d'origine, et

  • le nouvel objet est du même type que l'objet d'origine (en ignorant les qualificatifs cv de niveau supérieur), et

  • le type de l'objet d'origine n'est pas qualifié par const et, s'il s'agit d'un type de classe, ne contient aucun membre de données non statique dont le type est qualifié par const ou un type de référence, et

  • ni l'objet d'origine ni le nouvel objet n'est un sous-objet potentiellement chevauchant ([intro.object]).

Étant donné que votre objet est trivial, vous n'avez pas à vous soucier de [basic.life] / 5 et tant que vous satisfaites les puces de [basic.life] / 8, alors il est sûr.

NathanOliver
la source