C++/Zarządzanie pamięcią: Różnice pomiędzy wersjami
Błąd w nazwie języka programowania |
rozszerzenie opisu new |
||
Linia 75: | Linia 75: | ||
Kolejną korzyścią jest możliwość przeciążania. Jednak to już jest temat na inny rozdział. |
Kolejną korzyścią jest możliwość przeciążania. Jednak to już jest temat na inny rozdział. |
||
==Działanie w przypadku braku pamięci== |
|||
1. Domyślnie gdy przydział pamięci jest niemożliwy operator '''new''' zgłasza wyjątek '''std::bad_alloc''', np. |
|||
<source lang="cpp" highlight="1,7,10"> |
|||
#include <new> // wyjątek std::bad_alloc |
|||
#include <cstdio> |
|||
int main() { |
|||
try { |
|||
char* p = new char[1000000000000]; |
|||
} catch (std::bad_alloc& e) { |
|||
// std::bad_alloc::what |
|||
std::printf("Błąd alokacji: %s\n", e.what()); |
|||
} |
|||
return 0; |
|||
} |
|||
</source> |
|||
2. Można wyłączyć zgłaszanie wyjątku, zamiast tego w przypadku braku pamięci zostanie zwrócony pusty wskaźnik ('''nullptr'''). W tym celu po słowie kluczowym '''new''' trzeba podać symbol '''std::noexcept''', np. |
|||
<source lang="cpp" highlight="1,5,10"> |
|||
#include <new> // symbol std::nothrow |
|||
#include <cstdio> |
|||
int main() { |
|||
char* p = new (std::nothrow) char[1000000000000]; |
|||
if (p == nullptr) { |
|||
std::puts("Brak pamięci"); |
|||
} |
|||
return 0; |
|||
} |
|||
</source> |
|||
==Placement new== |
|||
Jak zostało powiedziane operator new wykonuje dwie operacje: |
|||
* zaalokowanie pamięci o żądanym rozmiarze, |
|||
* wywołanie konstruktorów (domyślnych lub wyspecyfikowanych). |
|||
Można jednak użyć specjalnego wywołania '''new''', tzw. "placement new", które jedynie wywołuje konstruktory na pamięci już zaalokowanej w innym miejscu; trzeba być jednak pewnym, że wskazany obszar pamięci jest odpowiedniego rozmiaru. |
|||
Jest to niezbyt powszechne użycie, ma też jedną wadę: nie działa operator '''delete''', trzeba ręcznie wywoływać destruktory obiektów. Zastosowanie tego mechanizmu ma głównie sens, gdy samodzielnie zarządzamy pamięcią, np. zawczasu rezerwujemy pamięć dla dużej liczby obiektów i w miarę potrzeb ją przydzielamy, oszczędzając tym samym czas na każdorazowe odwołanie do alokatora pamięci. |
|||
<source lang="cpp" highlight="12,14"> |
|||
#include <new> |
|||
#include <cstdlib> // malloc |
|||
class Klasa { |
|||
int numer; |
|||
}; |
|||
int main() { |
|||
void* wskaznik = malloc(sizeof(Klasa)); |
|||
Klasa* obiekt = new (wskaznik) Klasa; |
|||
obiekt->~Klasa(); |
|||
return 0; |
|||
} |
|||
</source> |
|||
<noinclude> |
<noinclude> |
Wersja z 18:49, 16 paź 2014
W języku C++ do alokowania pamięci na stercie służy operator new, a do zwalniania - delete. W C można również stosować funkcje malloc i free, jednak należy być ostrożnym. Najczęstszym błędem jest mieszanie operatorów new i delete z funkcjami malloc i free, np. zwalnianie pamięci zaalokowanej przez new przy pomocy free.
Rozważmy prosty przykład. Załóżmy, że chcemy stworzyć wektor 10 liczb typu całkowitego. Możemy to zrobić na dwa sposoby. W stylu znanym z języka C:
int *wektor = (int*) malloc (sizeof(int)*10);
free (wektor);
Albo w stylu C++:
int *wektor = new int[10];
delete [] wektor;
Od razu widać, że drugi zapis jest łatwiejszy i przyjemniejszy w użyciu. To jest podstawowa zaleta operatora new - krótszy zapis. Wystarczy wiedzieć jakiego typu ma być obiekt, który chcemy powołać do życia, nie martwiąc się o rozmiar alokowanego bloku pamięci. Za pomocą operatora new można również tworzyć tablice wielowymiarowe:
int **wektory = new int *[5];
for (int i = 0; i < 5; ++i)
wektory[i] = new int [10];
W ten sposób stworzono tablicę dwuwymiarową którą statycznie zadeklarowalibyśmy jako:
int wektory[5][10];
Jednak w przeciwieństwie do int wektory[5][10]
, która jest tablicą dwuwymiarową, nasze int **wektory
jest tablicą tablic i może być rozrzucone po całej pamięci.
Ilość elementów poszczególnych wymiarów nie musi być jednakowa. Można np zadeklarować tablicę taką:
int **wektory = new int *[2];
wektory[0] = new int [5];
wektory[1] = new int;
Przy takiej deklaracji pierwszy wiersz ma 5 elementów (tablica) a drugi to jeden element.
Deklaracja tablic o większej ilości wymiarów przebiega podobnie:
int ***wektory; // deklarujemy tablicę 3-wymiarową
wektory = new int **[5]; // pierwszy wymiar
wektory[0] = new int *[10]; // pierwszy element pierwszego wymiaru
wektory[1] = new int *[3]; // drugi element pierwszego wymiaru
....
wektory[0][0] = new int [3] // wymiar I = 0 -> wymiar II = 1 -> 3 elementy(tablica)
wektory[0][1] = new int [5] // wymiar I = 0 -> wymiar II = 3 -> 5 elementów(tablica)
wektory[1][0] = new int; // wymiar I = 1 -> wymiar II = 2 -> 1 element
...
Stosując ten sposób, ogólnie można deklarować tablice n-wymiarowe bez większego problemu.
Usuwanie tablic wielowymiarowych przebiega podobnie jak jednowymiarowych, z tą różnicą, że usuwanie zaczynamy od "najgłębszego" wymiaru:
delete wektory[1][0]; // kasujemy pojedynczą zmienną
delete [] wektory[0][1];
delete [] wektory[0][0];
// II wymiar
delete [] wektory[0];
delete [] wektory[1];
// I wymiar
delete [] wektory;
Zwrócić uwagę trzeba na dwie rzeczy:
delete []
używamy dla zmiennych tablicowych, adelete
dla pojedynczych zmiennych- Kolejność zwalniania wymiarów jest odwrotna niż ich tworzenia
Drugą zaletą jest fakt, że przy okazji alokacji pamięci możemy wywołać odpowiedni konstruktor inicjując wartości zmiennych obiektu, np.
Test *test = new Test(1,2);
zakładając, że obiekt Test posiada dwie zmienne typu całkowitego i zdefiniowany konstruktor Test(int,int)
.
Kolejną korzyścią jest możliwość przeciążania. Jednak to już jest temat na inny rozdział.
Działanie w przypadku braku pamięci
1. Domyślnie gdy przydział pamięci jest niemożliwy operator new zgłasza wyjątek std::bad_alloc, np.
#include <new> // wyjątek std::bad_alloc
#include <cstdio>
int main() {
try {
char* p = new char[1000000000000];
} catch (std::bad_alloc& e) {
// std::bad_alloc::what
std::printf("Błąd alokacji: %s\n", e.what());
}
return 0;
}
2. Można wyłączyć zgłaszanie wyjątku, zamiast tego w przypadku braku pamięci zostanie zwrócony pusty wskaźnik (nullptr). W tym celu po słowie kluczowym new trzeba podać symbol std::noexcept, np.
#include <new> // symbol std::nothrow
#include <cstdio>
int main() {
char* p = new (std::nothrow) char[1000000000000];
if (p == nullptr) {
std::puts("Brak pamięci");
}
return 0;
}
Placement new
Jak zostało powiedziane operator new wykonuje dwie operacje:
- zaalokowanie pamięci o żądanym rozmiarze,
- wywołanie konstruktorów (domyślnych lub wyspecyfikowanych).
Można jednak użyć specjalnego wywołania new, tzw. "placement new", które jedynie wywołuje konstruktory na pamięci już zaalokowanej w innym miejscu; trzeba być jednak pewnym, że wskazany obszar pamięci jest odpowiedniego rozmiaru.
Jest to niezbyt powszechne użycie, ma też jedną wadę: nie działa operator delete, trzeba ręcznie wywoływać destruktory obiektów. Zastosowanie tego mechanizmu ma głównie sens, gdy samodzielnie zarządzamy pamięcią, np. zawczasu rezerwujemy pamięć dla dużej liczby obiektów i w miarę potrzeb ją przydzielamy, oszczędzając tym samym czas na każdorazowe odwołanie do alokatora pamięci.
#include <new>
#include <cstdlib> // malloc
class Klasa {
int numer;
};
int main() {
void* wskaznik = malloc(sizeof(Klasa));
Klasa* obiekt = new (wskaznik) Klasa;
obiekt->~Klasa();
return 0;
}