2012-05-14 14 views
5

Mam pytanie dotyczące umieszczenia składni new w C++. Czy poniższe dwa fragmenty kodu są funkcjonalnie równoważne i mogą być używane zamiennie (nie sugeruję, że drugi powinien być używany, gdy pierwszy jest odpowiedni)?Nowy równoważnik zachowania miejsca docelowego

# 1

T* myObj = new T(); 
// Do something with myObj 
delete myObj; 

# 2

char* mem = new char[sizeof(T)]; 
T* myObj = new (mem) T(); 
// Do something with myObj 
myObj->~T(); 
delete[] mem; 

Czy coś powinienem być szczególnie ostrożny, gdy używam kwalifikacyjny nową składnię jak to?

Odpowiedz

11

Nie są one równoważne, ponieważ mają inne zachowanie, jeśli konstruktor lub destruktor zrzuca T.

new T() zwolni pamięć, która została przydzielona przed zezwoleniem na propagację wyjątku. char* mem = new char[sizeof(T)]; T* myObj = new (mem) T(); nie będzie (i jeśli wyraźnie nie zrobisz czegoś, aby upewnić się, że zostanie uwolniony, wystąpi wyciek). Podobnie, delete myObj zawsze zwalnia pamięć, niezależnie od tego, czy rzuca ~T().

dokładnym odpowiednikiem dla T* myObj = new T();/*other code*/delete myObj; byłoby coś takiego:

//When using new/delete, T::operator new/delete 
//will be used if it exists. 
//I don't know how do emulate this in 
//a generic way, so this code just uses 
//the global versions of operator new and delete. 
void *mem = ::operator new(sizeof(T)); 
T* myObj; 
try { 
    myObj = new (mem) T(); 
} 
catch(...) { 
    ::operator delete(mem); 
    throw; 
} 
/*other code*/ 
try { 
    myObj->~T(); 
    ::operator delete(mem); 
} 
catch(...) { 
    //yes there are a lot of duplicate ::operator deletes 
    //This is what I get for not using RAII): 
    ::operator delete(mem); 
    throw; 
} 
+0

Czy pamięć przydzielona przez 'nowy char [sizeof (T)] zostanie prawidłowo wyrównana? –

+3

@TadeuszKopec: Standard gwarantuje, że pamięć ma maksymalne możliwe wyrównanie. Ma to zastosowanie tylko w przypadku korzystania z naturalnych linii trasowania (tj. Wyświetlanych z wbudowanymi typami), jeśli korzystasz z pragmy w celu dostosowania wyrównania typu do, powiedzmy, dwukrotności maksymalnej, wszystkie zakłady są wyłączone. –

8

Skoro przydzielania pamięci surowe, bliżej równoznaczne byłoby:

void *mem = operator new(sizeof(T)); 
T *myobj = new(mem) T(); 

// ... 

myobj->~T(); 
operator delete(mem); 

Należy pamiętać, że jeśli już przeciążony ::operator new dla danej klasy, to użyje tej klasy operator new, gdzie twoje korzystając new char [] zignorowałbym to.

Edycja: chociaż powinienem dodać, że ignoruję tutaj możliwość wyjątków. @ Odpowiedź Mankarse'a wydaje mi się dość dobra.

0

na podstawowym poziomie, robisz to samo w obu sytuacjach: czyli jesteś uruchamianiu tego nowego obiektu na stercie i ci "zwalniam pamięć, ale w drugim przypadku (oczywiście) korzystasz z nowego operatora miejsca docelowego.

W tym przypadku oba z nich przyniosłyby takie same wyniki, minus różnice, jeśli konstruktor rzuci, jak wyjaśnił Mankarse.

Ponadto, nie powiedziałbym, że można ich używać zamiennie, ponieważ drugi przypadek nie jest realistycznym przykładem użycia placement new. Prawdopodobnie byłoby dużo sensu używać placement new w kontekście puli pamięci i jeśli tworzysz własny menedżer pamięci, dzięki czemu możesz umieścić wiele obiektów na przydzielonej pamięci (w moich testach ma tendencję do oszczędzania). 25% czasu procesora). Jeśli masz bardziej realistyczny przypadek użycia dla placement new, będzie o wiele więcej rzeczy, o które powinieneś się martwić.

0

Cóż, wstępnie alokujesz pamięć dla twojego obiektu T. I powinno być dobrze. Niemniej jednak ma sens, jeśli ponownie wykorzystasz przydzielony obszar jeszcze raz. W przeciwnym razie będzie wolniej.

0

Tak. Twój przykład jest zbyt prosty, aby to zademonstrować, ale pamięć, którą wcześniej przydzieliłeś, "mem", powinna zarządzać obiektem przechowywanym wewnątrz, "myObj."

Być może umieścić lepszego sposobu, scenariusz # 1 przydziela miejsca na stercie, i konstruuje przedmiot w tej przestrzeni. Scenariusz # 2 przydziela miejsca na stercie,«mem», a następnie konstruuje obiekt gdzieś wewnątrz tej przestrzeni.

teraz umieścić drugi obiekt gdzieś indziej wewnątrz „MEM”. To komplikuje, prawda?

budowa i zniszczenie myObj dzieją się identycznie w obu przypadkach (z wyjątkiem c ase twojego konstruktora rzucającego wyjątek, jak wskazał Mankarse), ale alokator zajmuje się twoim zarządzaniem pamięcią dla ciebie w scenariuszu 1, a nie w scenariuszu nr 2.

Należy więc uważnie zarządzać "memem". Jeden common approach jest następujący:

template<class T> void destroy(T* p, Arena& a) 
{ 
     if (p) { 
       p->~T();  // explicit destructor call 
       a.deallocate(p); 
     } 
}