Dynamiczne zarządzanie pamięcią¶
Rodzaje pamięci¶
Pamięć kodu – pamięć rezerwowana przez kompilator, w której umieszczony jest kod programu
Pamięć statyczna – pamięć zarezerwowana dla zmiennych globalnych lub statycznych
Pamięć automatyczna (stos) – rezerwowana na potrzeby wywołań funkcji (argumenty i zmienne lokalne)
Pamięć wolna (sterta)
reszta pamięci komputera
dostępem do pamięci wolnej zarządzają operatory
new
,new[]
,delete
idelete[]
Do dynamicznego zarządzania pamięcią wolną oraz jawnego tworzenia i usuwania obiektów służą dwa operatory new
i delete
.
Operatory te umożliwiają zarządzanie obiektami dowolnych klas lub typów wbudowanych, a także tablicami dowolnego typu.
Zastąpiły funkcje zarządzania pamięcią malloc()
i ``free()``znane z języka C.
Operatory new
i delete
są częścią specyfikacji języka C++, a nie biblioteki standardowej.
Operatory new i new[]¶
Operator new
jawnie przydziela pamięć obiektom wybranego typu.
float* pf = new float;
std::string* pstr1 = new string;
std::string* pstr2;
pstr2 = new string("hello");
Operator new[]
jest używany do przydzielania pamięci tablicom
char* s = new char[len+1]; // len+1 znaków
std::string* strings = new std::string[40];
float** values = new float*[10]; // 10 wskaźników na double
Operator new
w przeciwieństwie do funkcji malloc()
zwraca zawsze wskaźnik właściwego typu – nie jest wymagana jawna konwersja przed odwołaniem się do tak utworzonej zmiennej. Jeśli operator new
zostanie użyty w celu utworzenia nowego obiektu, obiekt ten zostanie zainicjowany. Typy wbudowane nie zostają zainicjowane. Nie ma potrzeby sprawdzania czy operator new
zwraca wartość nullptr
. W przypadku przydział pamięci lub utworzenie obiektu się nie powiodło rzucane są wyjątki. Operator new
może zostać także zaimplementowany przez programistę do tworzonych przez niego typów. Umożliwia to optymalizację zarządzania pamięcią przy zachowaniu standardowego interfejsu tworzenia obiektów.
Operatory delete
i delete[]
¶
Operator delete
zwalnia pamięć przydzieloną wcześniej za pomocą operatora new
.
float* pf = new float;
std::string* pstr1 = new string;
delete pf;
delete pstr1;
Operator delete[]
zwalnia pamięć dla tablic.
char* s = new char[len+1]; // len+1 znaków
std::string* strings = new std::string[40];
float** values = new float*[10];
delete [] s;
delete [] strings;
delete [] values;
Użycie operatora delete
w stosunku do obiektu, który nie został utworzony za pomocą operatora new
nie jest zdefiniowane i może być fatalne w skutkach. Operator delete
może być stosowany dla wskaźników o wartości nullptr
, 0 lub NULL
.
Person* ptr = nullptr;
if (is_satisfied())
ptr = new Person;
delete ptr; // poprawne, bez względu na to, czy pamięć została przydzielona
Problemy ze wskaźnikami¶
Podstawowe wytyczne bezpiecznego wykorzystywania wskaźników:
Nie uzyskuj dostępu do obiektów przez wskaźnik zerowy.
int* p = NULL; *p = 7; // Błąd!
Inicjuj swoje wskaźniki.
int* p; *p = 9; // Błąd!
Nie uzyskuj dostępu do nieistniejących elementów tablicy.
int a[10]; int* p = &a[10]; *p = 11; // Błąd! a[10] = 12; // Błąd!
Nie próbuj uzyskać dostępu poprzez usunięty wskaźnik.
int* p = new int(7); // ... delete p; // ... *p = 10; // Błąd!
Nie zwracaj wskaźnika do zmiennej lokalnej.
int* f() { int x = 7; return &x; } // ... int* p = f(); *p = 15; // Błąd!
Inteligentne wskaźniki¶
W C++11/14 należy unikać jawnych wywołań operatorów new
oraz delete
. W celu dynamicznego zarządzania pamięcią (zasobami) należy używać klas inteligentnych wskaźników:
std::unique_ptr<T>
- wskaźnik implementujący wyłączne prawo własności do zarządzanego zasobustd::shared_ptr<T>
- wskaźnik implementujący współdzielone prawo własności
Inteligentne wskaźniki w C++:
przeciążają operatory dereferencji
*
oraz->
upraszczają dynamiczne zarządzanie pamięcią i umożliwiają uniknięcie wycieków pamięci.
Wskaźnik std::unique_ptr
¶
std::unique<ptr>
implementuje wyłączne prawo własności do alokowanego dynamicznie obiektuDestruktor
std::unique<ptr>
zwalnia zasób (domyślnie wywołuje destruktor i zwalnia pamięć po obiekcie)Nie może być kopiowany, ale może być przesuwany - implementuje tzw. move semantics
#include <memory>
#include <iostream>
#include <string>
int main()
{
std::unique_ptr<std::string> ptr_word = std::make_unique<std::string>("smart_ptr");
std::cout << *ptr_word << " has length " << ptr_word->size() << std::endl;
} // automatyczne zwolnienie pamięci po obiekcie ptr_word