2010-12-12 5 views
5

Zdaję sobie sprawę, jest to niewskazane, a ja nie proponuje, aby to zrobić, ale jestem ciekaw, czy rzeczywiście następuje formalnie nielegalne:Czy ręczne symulowanie efektów operatora usuwania w C++ jest formalnie nielegalne?

#include <iostream> 

struct X 
{ 
    ~X() 
    { 
     std::cout << "~X()\n"; 
    } 
}; 

int main() 
{ 
    X *x = new X; 
    //delete x; 
    x->~X(); 
    ::operator delete(x); 
    return 0; 
} 

To moja zrozumienie, że delete x; jest równoważna wywołanie destruktora, a następnie wywołanie ::operator delete(x);, ale czy jest to zgodne z prawem, aby to zrobić ręcznie zgodnie ze standardem? Wiem, że jest to ważne, gdy korzystasz z nowego miejsca docelowego, ale co w przypadku bez umieszczania? Moje przeczucie jest takie, że może być nielegalne, ponieważ delete (i nie operator delete) musi być wykonane dla każdego new, ale chciałbym wiedzieć na pewno.

+0

czy pytasz, czy 'delete x;' jest równoważne wywołaniu destruktora, a następnie 'operator delete (x);' dla * tej konkretnej próbki kodu *, czy * ogólnie *? –

+0

@Ben: W tym konkretnym przykładzie kodu - ale interesuje mnie również ogólny przypadek (na który teraz zakładam, że odpowiedź brzmi "nie"). –

+0

Cieszę się, że zadałem to pytanie - w rzeczywistości jest o wiele więcej odpowiedzi, niż sobie wyobrażałem. –

Odpowiedz

3

Jestem prawie pewien, że nie jest to zgodne z normą. Nigdzie w standardzie (że jestem świadomy) to znaczy, że operand delete p; jest przekazywane bezpośrednio do funkcji dealokacji, a dla delete [] to prawie na pewno nie jestprzepuszcza niezmieniony.

Prawdopodobnie zadziała jednak na wszystkich praktycznych wdrożeniach.

Z pewnością nie powinieneś jawnie wywoływać funkcji globalnej dezalokacji. Jest w porządku w przykładzie, który nie posiada funkcji alokacji i dealokacji zdefiniowane przez użytkownika, ale nie w przypadku ogólnym (faktycznie, jest jakaś składnia wywoływania funkcji dealokacji klasy specyficzne, jeśli istnieje, a globalny inaczej?).

Ponadto, w ogólnym przypadku, wskaźnik przekazywany do funkcji dealokacji pewno nie jest operand usunięcia. Rozważmy:

struct A 
{ 
    virtual ~A() {} 
}; 

struct B1 : virtual A {}; 

struct B2 : virtual A {}; 

struct B3 : virtual A {}; 

struct D : virtual B1, virtual B2, virtual B3 {}; 

struct E : virtual B1, virtual D {}; 

int main(void) 
{ 
    B3* p = new D(); 
    p->~B3(); // should be ok, calling virtually 
    operator delete(p); // definitely NOT OK 

    return 0; 
} 
+0

+1, myślę, że zdecydowanie zgadzam się co do tego aspektu "nie powinienem" :) Faceci w comp.lang.C++ wspomnieli również, że zdefiniowane przez użytkownika 'operator new' i' operator delete' spowodowałyby problemy, więc wydaje się to głupie, nawet jeśli byłoby to legalne. Jeśli chodzi o pytanie o składnię - wiem, że możesz wywołać 'T :: operator delete', aby uzyskać specyfikację klasy T pod warunkiem, że istnieje. Może istnieć pewne metaprogramowanie szablonowe, które można zrobić, aby upewnić się, że globalne jest inaczej, ale nie jestem ekspertem w tej dziedzinie. To pewnie coś w rodzaju SFINAE, jeśli to możliwe, zgaduję. –

+1

"" Jestem pewien, że nie jest to zgodne z normą. "" Czy masz na myśli, że ** określony kod ** w pytaniu nie jest gwarantowany? Czy masz na myśli, że 'nowy X' nie może wywołać' operator new (sizeof (X)) '? – curiousguy

+0

@ciałoguy; Mam na myśli, że 'nowy X' może mieć inne efekty uboczne oprócz wywoływania funkcji alokacji i konstruktora, które wymagają późniejszego pasującego wyrażenia delete. –

2

Uważam, że nie powołujesz się na niezdefiniowane zachowanie, jeśli wywołasz destruktor i zwolnisz pamięć.

+0

Dzięki - nie jest to ostateczna odpowiedź, której szukałem, ale doceniam tę opinię. Poprosiłem o to na comp.lang.C++ na wypadek, gdyby ktoś mógł oświecić mnie rozdziałem i wersem ze standardu. –

+1

Zadzwoń do destruktora i zwolnij pamięć, tak. Ale musisz uwolnić odpowiednią pamięć, wywołując właściwą funkcję dealokacji * i * przekazując właściwy wskaźnik. Co jest dużo bardziej skomplikowane w ogólnym przypadku, niż sugeruje kod w tym pytaniu. –

0

Myślałem, że

::operator delete(x); 

jest taka sama jak

delete x; 

jeśli metoda delete nie jest przesłonięta przez X?

+2

To nie jest. Operator 'delete' wywołuje destruktor, jeśli wskaźnik nie ma wartości null, a następnie wywołuje funkcję dealokacji, jeśli wskaźnik nie ma wartości null, a może nawet jeśli jest. Parametr przekazany do funkcji dealokacji nie jest określony w standardzie. –

3

Jeśli zamierzasz w ten sposób, należy wymienić

X *x = new X; 

przez

X* x = static_cast<X*>(::operator new(sizeof(X))); 

try { new(x) X; } 
catch (...) { ::operator delete(x); throw; } 

które powinny być (proszę mnie poprawić, jeśli się mylę) spójne z telefonem podejście do destrukcji i w dużym stopniu równoważne funkcjonalności do new.

+0

Dzięki - za pomocą nowego miejsca docelowego (wiem, że to ważne). Byłem bardziej ciekawy, czy mieszanie i łączenie obu było nielegalne. –

+0

Jestem prawie pewien, że to jest dokładnie to, co robi "nowy" - chyba, że ​​dzieje się coś nowego "nowego". – curiousguy

1

@StuartGolodetz

Odbierz i odpowiedź na komentarz

Nie mogą być również niektóre szablon metaprogramowanie można zrobić, aby upewnić się, że globalny jeden nazywa się inaczej, ale nie jestem ekspert w tej sprawie. To pewnie coś w rodzaju SFINAE, jeśli to możliwe, zgaduję.

 
struct B { 
    virtual ~B(); 
}; 

struct D : B { 
    operator new (size_t); 
    operator delete (void*); 
}; 

B *p = new D; 

decltype(typeid(*p))::operator delete (...); // hypothetical C++++ 

Meta to!

+0

Używa to statycznego typu '* p', którym jest' B', prawda? I dlatego nazywa niewłaściwego deallocator? W rzeczywistości nie powinno się nawet kompilować, ponieważ 'B' nie ma operatora' usuń operatora'. –

+0

@BenVoigt Hum, wziąłeś to dosłownie? '* p ::' to nie jest nawet C++! – curiousguy

+0

Przypuszczam, że nie, ale "decltype (* p) ::" jest. –