Ist es sicher, die Platzierung neu auf "this" für triviale Objekte aufzurufen?

20

Ich weiß, dass diese Frage bereits mehrmals gestellt wurde, aber ich konnte keine Antwort für diesen speziellen Fall finden.

Angenommen, ich habe eine triviale Klasse, die keine Ressourcen besitzt und einen leeren Destruktor und einen Standardkonstruktor hat. Es verfügt über eine Handvoll Mitgliedsvariablen mit klasseninterner Initialisierung. keiner von ihnen ist const.

Ich möchte eine solche Klasse neu initialisieren und objektieren, ohne die deInitMethode von Hand zu schreiben . Ist es sicher, das so zu machen?

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

Ich kann kein Problem damit erkennen - das Objekt befindet sich immer im gültigen Zustand und zeigt thisimmer noch auf dieselbe Adresse. aber es ist C ++, also möchte ich sicher sein.

Amomum
quelle
2
Sind irgendwelche Mitglieder des Objekts const?
NathanOliver
2
Wenn dies gültig ist, wäre es gleichbedeutend mit *this = A{};?
Kevin
2
@Amomum *this = A{};bedeutet, this->operator=(A{});dh ein temporäres Objekt erstellen und zuweisen *this, wobei die Werte aller Datenelemente durch die temporären Werte ersetzt werden. Da dies das ist, was Sie wollen und (meiner Meinung nach) besser lesbar sind als ein neues Praktikum, würde ich stattdessen damit anfangen.
Kevin
1
@ Kevin oh, mein schlechtes, du hast recht. Dann - ich denke, es sollte gleich sein, wenn die Kopie entfernt wird?
Amomum
1
Anstatt die Klasse in Worten zu erklären, ist es viel besser, eine vollständige Klasse zu schreiben und nicht nur eine Methode. siehe sscce.org
BЈовић

Antworten:

17

Ähnlich wie bei der Legalität von delete thisist meines Wissens auch eine neue Platzierung thiszulässig. In Bezug darauf, ob thisoder andere bereits vorhandene Zeiger / Referenzen später verwendet werden können, gibt es einige Einschränkungen:

[basic.life]

Wenn nach Ablauf der Lebensdauer eines Objekts und vor der Wiederverwendung oder Freigabe des Speichers, in dem sich das Objekt befindet, ein neues Objekt an dem Speicherort erstellt wird, an dem sich das ursprüngliche Objekt befindet, ein Zeiger, der auf das ursprüngliche Objekt zeigt, ein Verweis darauf Der Verweis auf das ursprüngliche Objekt oder der Name des ursprünglichen Objekts verweist automatisch auf das neue Objekt und kann nach Beginn der Lebensdauer des neuen Objekts zum Bearbeiten des neuen Objekts verwendet werden, wenn:

  • Der Speicher für das neue Objekt überlagert genau den Speicherort, den das ursprüngliche Objekt belegt hat, und
  • Das neue Objekt ist vom selben Typ wie das ursprüngliche Objekt (ohne Berücksichtigung der Lebenslaufqualifizierer der obersten Ebene)
  • Der Typ des ursprünglichen Objekts ist nicht const-qualifiziert und enthält, wenn es sich um einen Klassentyp handelt, kein nicht statisches Datenelement, dessen Typ const-qualifiziert ist, oder einen Referenztyp
  • Weder das ursprüngliche noch das neue Objekt sind potenziell überlappende Unterobjekte ([intro.object]).

Die ersten beiden sind in diesem Beispiel erfüllt, die letzten beiden müssen jedoch berücksichtigt werden.

In Bezug auf den dritten Punkt sollte angesichts der Tatsache, dass die Funktion nicht const-qualifiziert ist, ziemlich sicher angenommen werden, dass das ursprüngliche Objekt nicht const ist. Der Fehler liegt auf der Anruferseite, wenn die Konstanz weggeworfen wurde. In Bezug auf const / reference member denke ich, dass dies überprüft werden kann, indem behauptet wird, dass dies zuweisbar ist:

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

Da Zuweisbarkeit eine Voraussetzung ist, können Sie natürlich stattdessen einfach das verwenden, von *this = {};dem ich erwarten würde, dass es dasselbe Programm erzeugt. Ein vielleicht interessanterer Anwendungsfall könnte darin bestehen, den Speicher *thisfür ein Objekt eines anderen Typs wiederzuverwenden (was die Anforderungen für die Verwendung thiszumindest ohne Neuinterpretation + Waschen nicht erfüllen würde ).

Ähnlich wie bei einer delete thisneuen Platzierung kann thisman kaum als "sicher" bezeichnen.

Eerorika
quelle
Interessant. Ist es möglich sicherzustellen, dass alle diese Bedingungen für einige Typmerkmale mit static_assert erfüllt sind? Ich bin mir nicht sicher, ob es einen über Const-Mitglieder gibt ...
Amomum
1
@ Amomum Ich denke nicht, dass es etwas ist, das getestet werden kann, wenn man ein Unterobjekt ist. Die const- oder Referenzmitglieder würden die Klasse nicht zuweisbar machen, was meiner Meinung nach für eine triviale Klasse nicht anders passieren kann.
Eerorika
Bedeutet dies, dass striktes Aliasing in Bezug auf die Konstanz ins Spiel kommt? Können Sie ein Beispiel nennen, wo dies ins Spiel kommen könnte?
Darune
1
@darune Erstellen Sie ein const-Objekt, platzieren Sie es neu, verwenden Sie den ursprünglichen Namen des Objekts (oder einen bereits vorhandenen Zeiger oder eine Referenz), und das Verhalten ist undefiniert. Ziemlich genau so wie die strikte Aliasing-Optimierung, wenn nicht sogar gleich.
Eerorika
1
Die Umkehrung von delete ptrist new T(). Die Umkehrung von new(ptr)T{}ist ptr->~T();. stackoverflow.com/a/8918942/845092
Mooing Duck
7

Die Regeln, die dies abdecken, sind in [basic.life] / 5

Ein Programm kann die Lebensdauer eines Objekts beenden, indem es den Speicher, den das Objekt belegt, wiederverwendet oder den Destruktor explizit für ein Objekt eines Klassentyps aufruft. Für ein Objekt eines Klassentyps muss das Programm den Destruktor nicht explizit aufrufen, bevor der Speicher, den das Objekt belegt, wiederverwendet oder freigegeben wird. Wenn der Destruktor jedoch nicht explizit aufgerufen wird oder wenn kein Löschausdruck zum Freigeben des Speichers verwendet wird, wird der Destruktor nicht implizit aufgerufen, und jedes Programm, das von den vom Destruktor verursachten Nebenwirkungen abhängt, weist ein undefiniertes Verhalten auf.

und [basic.life] / 8

Wenn nach Ablauf der Lebensdauer eines Objekts und vor der Wiederverwendung oder Freigabe des Speichers, in dem sich das Objekt befindet, ein neues Objekt an dem Speicherort erstellt wird, an dem sich das ursprüngliche Objekt befindet, ein Zeiger, der auf das ursprüngliche Objekt zeigt, ein Verweis darauf Der Verweis auf das ursprüngliche Objekt oder der Name des ursprünglichen Objekts verweist automatisch auf das neue Objekt und kann nach Beginn der Lebensdauer des neuen Objekts zum Bearbeiten des neuen Objekts verwendet werden, wenn:

  • Der Speicher für das neue Objekt überlagert genau den Speicherort, den das ursprüngliche Objekt belegt hat, und

  • Das neue Objekt ist vom selben Typ wie das ursprüngliche Objekt (ohne Berücksichtigung der Lebenslaufqualifizierer der obersten Ebene)

  • Der Typ des ursprünglichen Objekts ist nicht const-qualifiziert und enthält, wenn es sich um einen Klassentyp handelt, kein nicht statisches Datenelement, dessen Typ const-qualifiziert ist, oder einen Referenztyp

  • Weder das ursprüngliche noch das neue Objekt sind potenziell überlappende Unterobjekte ([intro.object]).

Da Ihr Objekt trivial ist, müssen Sie sich keine Sorgen um [basic.life] / 5 machen. Solange Sie die Aufzählungspunkte von [basic.life] / 8 erfüllen, ist es sicher.

NathanOliver
quelle