Czy bezpieczne jest nazywanie umieszczania nowego na `this` dla trywialnych obiektów?

20

Wiem, że to pytanie zostało już zadane kilka razy, ale nie mogłem znaleźć odpowiedzi na ten konkretny przypadek.

Powiedzmy, że mam trywialną klasę, która nie posiada żadnych zasobów i ma pusty destruktor i domyślny konstruktor. Ma garść zmiennych składowych z inicjalizacją w klasie; żaden z nich nie jest const.

Chcę ponownie zainicjować i sprzeciwić się takiej klasie bez deInitręcznego pisania metody. Czy można to zrobić w ten sposób?

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

Nie widzę z tym żadnego problemu - obiekt jest zawsze w poprawnym stanie, thiswciąż wskazuje ten sam adres; ale to jest C ++, więc chcę być pewien.

Amomum
źródło
2
Czy są jakieś elementy obiektu const?
NathanOliver
2
Jeśli jest to poprawne, czy byłoby to równoważne *this = A{};?
Kevin
2
@Amomum *this = A{};oznacza, this->operator=(A{});tj. Stwórz obiekt tymczasowy i przypisz go *this, zastępując wartości wszystkich elementów danych wartościami tymczasowymi. Ponieważ tego właśnie chcesz i jest (moim zdaniem) bardziej czytelna niż nowe miejsce docelowe, wybrałbym to zamiast tego.
Kevin
1
@Kevin, o mój Boże, masz rację. Niż - myślę, że powinna być równa, jeśli kopia jest pomijana?
Amomum
1
zamiast wyjaśniać klasę słowami, o wiele lepiej jest napisać całą klasę, a nie tylko jedną metodę. patrz sscce.org
BЈовић

Odpowiedzi:

17

O ile mi wiadomo, podobnie jak legalność delete this, nowe miejsce thisjest dozwolone. Ponadto, jeśli chodzi o to this, czy lub inne wcześniej istniejące wskaźniki / odniesienia mogą być później użyte, istnieje kilka ograniczeń:

[basic.life]

Jeśli po zakończeniu okresu użytkowania obiektu i przed ponownym użyciem lub zwolnieniem pamięci zajmowanej przez obiekt zajmowany przez obiekt zostanie utworzony nowy obiekt w miejscu przechowywania, w którym zajmowany był obiekt oryginalny, wskaźnik wskazujący na obiekt oryginalny, odniesienie, które w odniesieniu do oryginalnego obiektu lub nazwa oryginalnego obiektu będzie automatycznie odnosić się do nowego obiektu, a po rozpoczęciu życia nowego obiektu można go używać do manipulowania nowym obiektem, jeśli:

  • pamięć dla nowego obiektu dokładnie nakłada się na miejsce przechowywania, które zajmował oryginalny obiekt, oraz
  • nowy obiekt jest tego samego typu co obiekt oryginalny (ignorując kwalifikatory cv najwyższego poziomu), oraz
  • typ oryginalnego obiektu nie jest const-kwalifikowany, a jeśli typ klasy, nie zawiera żadnego niestatycznego elementu danych, którego typ jest const-kwalifikowany lub typu referencyjnego, oraz
  • ani oryginalny obiekt, ani nowy obiekt nie są potencjalnie nakładającymi się podobiektami ([intro.object]).

Pierwsze dwa są spełnione w tym przykładzie, ale dwa ostatnie będą musiały zostać wzięte pod uwagę.

W odniesieniu do trzeciego punktu, biorąc pod uwagę, że funkcja nie jest zakwalifikowana do const, należy całkiem bezpiecznie założyć, że oryginalny obiekt nie jest const. Błąd jest po stronie wywołującej, jeśli constness został odrzucony. Jeśli chodzi o członka const / referencyjnego, myślę, że można to sprawdzić, stwierdzając, że można to przypisać:

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

Oczywiście, ponieważ możliwość przypisania jest wymogiem, możesz po prostu użyć tego, *this = {};którego spodziewałbym się stworzyć ten sam program. Być może bardziej interesującym przypadkiem może być ponowne użycie pamięci *thisdla obiektu innego typu (co nie spełniłoby wymagań dotyczących używania this, przynajmniej bez ponownej interpretacji + prania).

Podobnie jak w przypadku delete thisnowego miejsca docelowego z thistrudem można je opisać jako „bezpieczne”.

eerorika
źródło
Ciekawy. Czy można upewnić się, że wszystkie te warunki są spełnione z static_assert dla niektórych cech typu? Nie jestem pewien, czy jest coś o stałych członkach ...
Amomum
1
@Amomum Nie sądzę, że bycie podobiektem jest czymś, co można przetestować. Stałe lub referencyjne elementy sprawiłyby, że klasa nie byłaby możliwa do przypisania, co nie wydaje mi się, że mogłoby się zdarzyć inaczej dla trywialnej klasy.
eerorika
czy to oznacza, że ​​w grę wchodzi stosowanie ścisłego aliasingu? czy możesz podać przykład, w jaki sposób może to mieć znaczenie?
darune
1
@darune Utwórz na stałe obiekt, umieszczając na nim nowy, użyj oryginalnej nazwy obiektu (lub wcześniej istniejącego wskaźnika lub referencji), a zachowanie będzie niezdefiniowane. W zasadzie taki sam efekt, jak ścisła optymalizacja aliasingu, jeśli nie zupełnie taka sama.
eerorika
1
Odwrotnością delete ptrjest new T(). Odwrotnością new(ptr)T{}jest ptr->~T();. stackoverflow.com/a/8918942/845092
Kaczka Mooing
7

Reguły, które to obejmują, znajdują się w [basic.life] / 5

Program może zakończyć żywotność dowolnego obiektu przez ponowne użycie pamięci zajmowanej przez obiekt lub przez jawne wywołanie destruktora dla obiektu typu klasy. W przypadku obiektu typu klasy program nie musi jawnie wywoływać destruktora przed ponownym użyciem lub zwolnieniem pamięci zajmowanej przez obiekt; jednak jeśli nie ma wyraźnego wywołania destruktora lub jeśli do zwolnienia pamięci nie jest używane wyrażenie kasujące, destruktor nie jest domyślnie wywoływany, a dowolny program zależny od skutków ubocznych wywoływanych przez destruktor ma nieokreślone zachowanie.

i [basic.life] / 8

Jeśli po zakończeniu okresu użytkowania obiektu i przed ponownym użyciem lub zwolnieniem pamięci zajmowanej przez obiekt zajmowany przez obiekt zostanie utworzony nowy obiekt w miejscu przechowywania, w którym zajmowany był obiekt oryginalny, wskaźnik wskazujący na obiekt oryginalny, odniesienie, które w odniesieniu do oryginalnego obiektu lub nazwa oryginalnego obiektu będzie automatycznie odnosić się do nowego obiektu, a po rozpoczęciu życia nowego obiektu można go używać do manipulowania nowym obiektem, jeśli:

  • pamięć dla nowego obiektu dokładnie nakłada się na miejsce przechowywania, które zajmował oryginalny obiekt, oraz

  • nowy obiekt jest tego samego typu co obiekt oryginalny (ignorując kwalifikatory cv najwyższego poziomu), oraz

  • typ oryginalnego obiektu nie jest const-kwalifikowany, a jeśli typ klasy, nie zawiera żadnego niestatycznego elementu danych, którego typ jest const-kwalifikowany lub typu referencyjnego, oraz

  • ani oryginalny obiekt, ani nowy obiekt nie są potencjalnie nakładającymi się podobiektami ([intro.object]).

Ponieważ twój obiekt jest trywialny, nie musisz się martwić o [basic.life] / 5 i dopóki spełniasz warunki punktorów z [basic.life] / 8, to jest bezpieczny.

NathanOliver
źródło