Alokacja pamięci

W języku C++ istnieje możliwość przydzielania (alokowanie) pamięci – możemy ją przydzielać w czasie wykonywania programu zmiennej lub tablicy. Proces ten jest znany pod nazwą dynamiczna alokacja pamięci.

Zapoznaj się z kodem:

Linia 1. int asterysk wskaznik znak równości new int średnik.
  • Deklarujemy zmienną wskaznik jako wskaźnikwskaźnikwskaźnik do zmiennej typu int.

  • Używamy operatora new, by zaalokować dynamicznie pamięć RAM dla pojedynczej zmiennej typu int.

  • Przypisujemy adresadres pamięciadres nowo zaalokowanej pamięci do zmiennej wskaźnikowej wskaznik.

W efekcie zmienna wskaznik przechowuje adres nowo zaalokowanej pamięci, gdzie może być przechowywana wartość typu int.

W poprzednim kroku zadeklarowaliśmy zmienną o nazwie wskaznik. Jak wyjaśniliśmy, wskazuje ona na fragment pamięci, w której przechowywana jest liczba całkowita. Jeżeli chcemy przypisać wartość liczbową do zmiennej wskaźnikowej, nie możemy wykorzystać do tego prostej operacji przypisania. Zrobimy to, wykorzystując następującą instrukcję:

Linia 1. asterysk wskaznik znak równości 24 średnik.
  • Używamy operatora dereferencji * przed zmienną wskaznik, co oznacza, że chcemy odwołać się do wartości przechowywanej pod adresem wskazywanym przez zmienną wskaznik.

  • Przypisujemy wartość 24 do zmiennej, do której wskazuje wskaźnik wskaznik.

W efekcie, wartość 24 jest przypisywana do zmiennej przechowywanej pod adresem wskazywanym przez wskaznik, czyli do dynamicznie zaalokowanej zmiennej typu int.

Podsumowując, te dwie linijki kodu alokują dynamicznie pamięć dla zmiennej typu int, a następnie przypisują wartość 24 do tej zmiennej poprzez wskaźnik.

Operator new w języku C++ jest używany do dynamicznej alokacji pamięci. Jego głównym zadaniem jest zaalokowanie odpowiedniej ilości pamięci na stosie i zwrócenie wskaźnika do tej pamięci. Pamięć zaalokowana przez operator new pozostaje zarezerwowana do momentu jej jawnego zwolnienia. Operator new nie tylko alokuje pamięć, ale także automatycznie wywołuje konstruktor obiektu, jeśli jest to obiekt klasy.

Ważne!

Więcej informacji na temat konstruktorów znajdziesz w e‑materiale Konstruktory i destruktory w języku C++PHF1hggdKKonstruktory i destruktory w języku C++.

Zarządzanie pamięcią

Specyfikacja języka C++ nie mówi o przydzielaniu pamięci na stosie czy stercie. Zamiast tego miejsce, w którym dany obiekt (dane zdefiniowane w programie) jest przechowywany w pamięci, zależy od kontekstu, według którego obiekt został zdefiniowany. W języku C++ każdy obiekt może mieć jeden z czterech poniższych cyklów istnienia:

  1. Automatyczny - pamięć przydzielana jest obiektowi w momencie utworzenia go dla lokalnego zakresu, a zwalniana na koniec zakresu. Zwykle automatyczny cykl życia jest obsługiwany przez stos wywołań działający według zasady LIFOLIFO (ang. Last In, First Out)LIFO. Zakres rozumiemu jako blok programu (to, co znajduje się między nawiasami klamrowymi).

  2. Dynamiczny - blok pamięci jest rezerwowany dla obiektu słowem kluczowym new, a zwalniany operatorem delete lub delete[]. Zaalokowana pamięć pozostaje zarezerwowana do końca działania programu lub do momentu gdy jej nie zwolnimy. W większości programów obsługiwany jest przez stertę.

  3. Statyczny - miejsce w pamięci dla obiektu jest automatycznie rezerwowane przy uruchomieniu programu, a zwalniane gdy program zakończy działanie.

  4. Wątkowy (od C++11) - rezerwuje blok pamięci dla obiektu, gdy wątek zostaje rozpoczęty, a dealokuje automatycznie pod koniec wątku.

Zwalnianie pamięci

W przeciwieństwie do języków takich jak Java lub Python , język C++ nie ma wbudowanego kolektora śmieci automatycznie zarządzającego pamięcią. Z tego powodu musimy samodzielnie zwolnić dynamicznie zarezerwowaną pamięć dla każdej zmiennej, która nie będzie już potrzebna.

Blok pamięci, który nie został zwolniony w trakcie uruchomiania, pozostaje zajęty przez cały okres działania programu. W niektórych przypadkach (gdy wyciek jest szczególnie duży) może doprowadzić to do obniżenia wydajności innych aplikacji lub systemu operacyjnego.

Operator delete zwalnia pamięć i wywołuje destruktor dla pojedynczego obiektu utworzonego za pomocą operatora new.

Linia 1. int asterysk wskaznik znak równości new int średnik. Linia 2. delete wskaznik średnik.
Ważne!

Więcej informacji na temat destruktorów znajdziesz w e‑materiale Konstruktory i destruktory w języku C++PHF1hggdKKonstruktory i destruktory w języku C++.

Operator delete[] zwalnia pamięć i wywołuje destruktory dla tablicy obiektów utworzonych za pomocą new[].

Linia 1. int asterysk wskaznik znak równości new int otwórz nawias kwadratowy 4 zamknij nawias kwadratowy średnik. Linia 2. delete otwórz nawias kwadratowy zamknij nawias kwadratowy wskaznik średnik.
Ważne!

Przykład działania alokacji oraz zwalniania pamięci omawiamy we fragmencie dotyczącym tablic dynamicznych.

Zmienne dynamiczne i statyczne

Zmienna dynamiczna to pewien rodzaj zmiennej, której przestrzeń w pamięci komputera jest alokowana w trakcie działania programu (a nie w momencie kompilacji). Dostęp do tej przestrzeni pamięci jest możliwy poprzez wskaźnik. Alokacja pamięci dla zmiennej dynamicznej odbywa się za pomocą operatora new, a zwolnienie pamięci przy użyciu operatora delete. Dzięki zmiennym dynamicznym program może elastycznie zarządzać pamięcią, alokując i zwalniając ją w zależności od bieżących potrzeb.

Zmienna statyczna to taka zmienna, której przestrzeń w pamięci jest alokowana na etapie kompilacji programu i pozostaje zaalokowana przez cały czas jego działania. Zmienne statyczne mogą być deklarowane na poziomie globalnym, w przestrzeni nazw, wewnątrz klas jako składowe statyczne lub lokalnie wewnątrz funkcji jako zmienne lokalne statyczne. W przeciwieństwie do zmiennych dynamicznych dostęp do zmiennych statycznych odbywa się bezpośrednio przez ich nazwy, a nie poprzez wskaźniki.

Wskaźnik przechowuje adres w pamięci komputera, gdzie zapisana jest jakaś wartość lub obiekt.

Wskaźnik to zmienna, która zamiast przechowywać bezpośrednio daną wartość (jak liczba czy znak), przechowuje adres, pod którym ta wartość się znajduje w pamięci komputera. Dzięki temu za pomocą wskaźnika możemy wskazać na miejsce, gdzie coś jest zapisane, co jest szczególnie przydatne w przypadku pracy z zmiennymi dynamicznymi.

Alokacja i dealokacja pamięci dla zmiennych statycznych jest zarządzana automatycznie, co oznacza, że programista nie musi jawnie alokować lub zwalniać pamięci za pomocą operatorów newdelete. Zmienne statyczne zadeklarowane poza funkcjami (na poziomie globalnym lub jako składowe klas) są inicjalizowane do wartości zerowych (dla typów podstawowych) lub konstruktorów domyślnych (dla obiektów) jeśli nie są jawnie inicjalizowane.

Przykład zastosowania wskaźnika, zmiennej statycznej i zmiennej dynamicznej:

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 2. using namespace std średnik. Linia 4. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. int liczba znak równości 10 średnik prawy ukośnik prawy ukośnik Zmienna statyczna liczba z wartością 10 kropka. Linia 6. int asterysk wskaznikNaLiczbe znak równości ampersant liczba średnik prawy ukośnik prawy ukośnik Tworzymy wskaźnik wskaznikNaLiczbe przecinek który przechowuje adres zmiennej liczba kropka. Linia 8. prawy ukośnik prawy ukośnik Przykład użycia wskaźnika do dostępu do zmiennej. Linia 9. prawy ukośnik prawy ukośnik otwórz nawias okrągły przed wskaźnikiem należy użyć operatora asterysk zamknij nawias okrągły dwukropek. Linia 10. cout otwórz nawias ostrokątny otwórz nawias ostrokątny asterysk wskaznikNaLiczbe otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik prawy ukośnik prawy ukośnik Drukuje wartość przechowywaną pod adresem przecinek na który wskazuje wskaźnik przecinek czyli 10 kropka. Linia 12. prawy ukośnik prawy ukośnik Demonstracja użycia zmiennej dynamicznej dwukropek. Linia 13. int asterysk dynamicznaZmienna znak równości new int otwórz nawias okrągły 20 zamknij nawias okrągły średnik prawy ukośnik prawy ukośnik Alokacja zmiennej dynamicznej z wartością 20 kropka. Linia 14. cout otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów Wartość zmiennej dynamicznej dwukropek cudzysłów otwórz nawias ostrokątny otwórz nawias ostrokątny asterysk dynamicznaZmienna średnik prawy ukośnik prawy ukośnik Dostęp do wartości zmiennej dynamicznej przez wskaźnik kropka. Linia 15. delete dynamicznaZmienna średnik prawy ukośnik prawy ukośnik Zwolnienie pamięci przydzielonej dla zmiennej dynamicznej kropka. Linia 17. return 0 średnik. Linia 18. zamknij nawias klamrowy.

Wynik działania programu:

Linia 1. 10. Linia 2. Wartość zmiennej dynamicznej dwukropek 20.
Ważne!

Znak * przez zmienną jest operatorem wskaźnika, który może być stosowany do deklaracji, dostępu i manipulacji wskaźnikami oraz do alokacji i zwalniania dynamicznej pamięci.

Druga linia wyniku, Wartość zmiennej dynamicznej: 20, jest rezultatem utworzenia zmiennej dynamicznej za pomocą operatora new, inicjalizacji jej wartością 20, a następnie wypisania tej wartości. W tym przypadku wskaźnik dynamicznaZmienna przechowuje adres zmiennej dynamicznej alokowanej w pamięci sterty, a używając tego wskaźnika z operatorem dereferencji (*dynamicznaZmienna), program wypisuje jej wartość. Następnie pamięć alokowana dynamicznie jest zwalniana za pomocą operatora delete, co jest kluczowe dla uniknięcia wycieków pamięci.

Pierwsza linia wyniku, czyli 10, jest wynikiem wypisania wartości zmiennej liczba, do której dostęp uzyskano za pomocą wskaźnika wskaznikNaLiczbe. Oznacza to, że korzystamy z samego adresu pamięci przechowywanego przez wskaźnik, aby uzyskać dostęp do danych zapisanych pod tym adresem. Wskaźnik służy jako pośrednik do odniesienia się do miejsca w pamięci, gdzie przechowywane są dane, zamiast przechowywać same dane bezpośrednio.

Wskaźnik ten został zainicjowany adresem zmiennej liczba, a następnie użyto go do odczytania wartości tej zmiennej (*wskaznikNaLiczbe), co pokazuje, jak wskaźniki mogą być używane do bezpośredniego dostępu i manipulacji wartościami zmiennych.

Pokazaliśmy w ten sposób dwa podstawowe zastosowania wskaźników w języku C++, czyli dostęp do zmiennych oraz zarządzanie pamięcią dynamiczną. Wskaźniki pozwalają na bezpośrednią manipulację pamięcią i adresami, co jest szczególnie ważne w kontekście zarządzania zasobami i optymalizacji. Niepoprawne użycie wskaźników może prowadzić do błędów takich jak wycieki pamięci, naruszenia dostępu do pamięci (z ang. segmentation faults) lub nieprzewidywalne zachowanie programu.

Sytuacje, gdy używamy wskaźników:

  • Chcemy zmieniać wartości zmiennych z innych funkcji (ponieważ przekazujemy do funkcji adres, a nie kopię wartości).

  • Zarządzamy pamięcią dynamicznie – kiedy chcemy sami decydować, kiedy coś zajmuje miejsce w pamięci i kiedy to miejsce zwalniamy.

  • Pracujemy z tablicami i innymi strukturami danych, gdzie wskaźniki pozwalają na efektywną manipulację danymi.

Rodzaje tablic

Tablice statyczne

Przypomnijmy sobie najważniejsze informacje na temat tablic statycznych (możesz również wrócić do e‑materiału Podstawowe struktury danych: tablicaP1HRyIBljPodstawowe struktury danych: tablica.

Tablice statyczne to jedna z podstawowych struktur danych w języku C++, która pozwala przechowywać zbiory elementów tego samego typu w ciągłym bloku pamięci. Są one nazywane statycznymi, ponieważ ich rozmiar musi być znany już w momencie kompilacji programu i nie może się zmieniać w trakcie jego działania.

Gdy deklarujesz tablicę statyczną, musisz określić jej rozmiar. Język C++ alokuje miejsce w pamięci na całą tablicę jako ciągły blok. Każdy element tablicy ma swój unikalny indeks, zaczynając od 0 dla pierwszego elementu, 1 dla drugiego i tak dalej. Umożliwia to prostu dostęp do każdego elementu tablicy.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 5. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 6. int mojaTablica otwórz nawias kwadratowy 5 zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik Deklaracja tablicy statycznej o rozmiarze 5 kropka. Linia 8. prawy ukośnik prawy ukośnik Wypełnienie tablicy wartościami kropka. Linia 9. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny 5 średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 10. mojaTablica otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości i asterysk 10 średnik. Linia 11. zamknij nawias klamrowy. Linia 13. prawy ukośnik prawy ukośnik Wypisanie wartości przechowywanych w tablicy kropka. Linia 14. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny 5 średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 15. cout otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów Element otwórz nawias kwadratowy cudzysłów otwórz nawias ostrokątny otwórz nawias ostrokątny i otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów zamknij nawias kwadratowy dwukropek cudzysłów otwórz nawias ostrokątny otwórz nawias ostrokątny mojaTablica otwórz nawias kwadratowy i zamknij nawias kwadratowy otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 16. zamknij nawias klamrowy. Linia 18. return 0 średnik. Linia 19. zamknij nawias klamrowy.

W tym przykładzie zmienna mojaTablica jest pięcioelementową tablicą liczb całkowitych. Najpierw wypełniamy ją wartościami za pomocą pętli for, a następnie iterujemy przez tablicę, aby wypisać jej zawartość.

Wynik działania programu:

Linia 1. Element otwórz nawias kwadratowy 0 zamknij nawias kwadratowy dwukropek 0. Linia 2. Element otwórz nawias kwadratowy 1 zamknij nawias kwadratowy dwukropek 10. Linia 3. Element otwórz nawias kwadratowy 2 zamknij nawias kwadratowy dwukropek 20. Linia 4. Element otwórz nawias kwadratowy 3 zamknij nawias kwadratowy dwukropek 30. Linia 5. Element otwórz nawias kwadratowy 4 zamknij nawias kwadratowy dwukropek 40.

W kontekście zmiennych statycznych w tablicach statycznych, pamięć jest zarządzana na zasadach bardzo podobnych do tych dla pojedynczych zmiennych statycznych. Jest jednak kilka istotnych różnic.

Podobnie jak pojedyncze zmienne statyczne, pamięć dla tablic statycznych jest alokowana na etapie kompilacji programu. Rozmiar tablicy musi być znany w czasie kompilacji, co oznacza, że nie można go zmienić w czasie działania programu.

Tablica statyczna zajmuje ciągły obszar w pamięci, który jest wystarczająco duży, aby pomieścić wszystkie jej elementy. Pamięć ta jest zwykle alokowana na stosie (dla tablic zadeklarowanych lokalnie w funkcji) lub w sekcji danych programu (dla tablic globalnych lub statycznych w klasach).

Elementy tablicy statycznej są automatycznie inicjalizowane na wartości domyślne (np. 0 dla typów liczbowych, false dla bool, null dla wskaźników) jeśli nie są jawnie inicjalizowane w kodzie. Programista może jawnie zainicjować tablicę przy jej deklaracji, określając wartości dla niektórych lub wszystkich jej elementów.

Tablice statyczne zadeklarowane lokalnie w funkcji są dostępne tylko w obrębie tej funkcji, ale ich wartości są zachowywane między kolejnymi wywołaniami funkcji, w przeciwieństwie do zmiennych automatycznych. Dla tablic globalnych lub statycznych w klasach, pamięć jest alokowana na cały czas działania programu, a więc tablice te są dostępne przez cały czas jego wykonania.

Pamięć zajmowana przez tablice statyczne jest automatycznie zwalniana przez środowisko wykonawcze C++ po zakończeniu działania programu lub przy wyjściu z bloku, w którym tablica została zadeklarowana (dla tablic lokalnych).

Tablice dynamiczne

Wskaźnik może nie tylko przechowywać adres pojedynczej zmiennej, ale również pozwala deklarować tablice o rozmiarze określanym w trakcie działania programu.

Ważne!

W przypadku tablic dynamicznych należy pamiętać o dwóch istotncyh poleceniach, których używamy:

  • new – operator alokuje pamięć dla tablicy dynamicznej;

  • delete – operator jest używany do zwolnienia pamięci zaalokowanej dla tablicy dynamicznej.

Operator new:

Linia 1. typ asterysk nazwa podkreślnik tablicy znak równości new typ otwórz nawias kwadratowy rozmiar zamknij nawias kwadratowy średnik.

Wyjaśnienie:

  • typ to typ danych, który ma być przechowywany w tablicy.

  • nazwa_tablicy to nazwa wskaźnika, który będzie wskazywał na pierwszy element tablicy.

  • rozmiar to liczba elementów w tablicy.

Operator new zwraca wskaźnik na pierwszy element nowo utworzonej tablicy.

Operator delete:

Linia 1. delete otwórz nawias kwadratowy zamknij nawias kwadratowy nazwa podkreślnik tablicy średnik.

Wyjaśnienie:

  • nazwa_tablicy to nazwa wskaźnika, który wskazuje na pierwszy element tablicy.

Operator delete[] informuje kompilator, że chcemy zwolnić pamięć zaalokowaną dla całej tablicy, a nie tylko dla pojedynczego elementu.

Po użyciu operatora delete[], pamięć zaalokowana dla tablicy jest zwalniana, a wskaźnik staje się nieważny. Jednocześnie wszystkie elementy tablicy stają się niedostępne.

Operator new umożliwia alokację pamięci dla tablic obiektów dowolnego typu. Aby zadeklarować tablicę dynamiczną typu int, należy przy deklaracji umieścić rozmiar tablicy wewnątrz nawiasów kwadratowych. Przy deklaracji tablicy dynamicznej operator new zwraca adres pierwszego elementu tablicy.

Linia 1. int n znak równości 10 średnik. Linia 2. int asterysk tablica znak równości new int otwórz nawias kwadratowy n zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik tablica dynamiczna o n wyrazach.

Do wyrazów tablic dynamicznych odwołujemy się identycznie, jak w przypadku tablic statycznych.

Linia 1. tablica otwórz nawias kwadratowy 4 zamknij nawias kwadratowy znak równości 6. Linia 2. cout otwórz nawias ostrokątny otwórz nawias ostrokątny tablica otwórz nawias kwadratowy 4 zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik wynik znak równości 6.

Dynamicznie można deklarować nie tylko tablice jednowymiarowe, ale także tablice dwu i więcej wymiarowe. Wtedy wskaźnik do tablicy dwuwymiarowej będzie typu int**, a dla każdego elementu tablicy (typu int*) należy zadeklarować nową tablicę dynamiczną.

Linia 1. int n znak równości 10 średnik. Linia 2. int asterysk asterysk tablica znak równości new int asterysk otwórz nawias kwadratowy n zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik tablica dynamiczna wskaźników. Linia 3. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny n średnik i plus plus zamknij nawias okrągły. Linia 4. otwórz nawias klamrowy. Linia 5. tablica otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości new int otwórz nawias kwadratowy n zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik deklaracja i minus tej tablicy. Linia 6. zamknij nawias klamrowy.
Polecenie 1

Przetestuj działanie programu:

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 2. using namespace std średnik. Linia 4. int main otwórz nawias okrągły zamknij nawias okrągły. Linia 5. otwórz nawias klamrowy. Linia 6. int n znak równości 10 średnik. Linia 7. int asterysk asterysk tablica znak równości new int asterysk otwórz nawias kwadratowy n zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik tablica dynamiczna wskaźników. Linia 9. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny n średnik i plus plus zamknij nawias okrągły. Linia 10. otwórz nawias klamrowy. Linia 11. tablica otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości new int otwórz nawias kwadratowy n zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik deklaracja i minus tej tablicy. Linia 12. zamknij nawias klamrowy. Linia 14. tablica otwórz nawias kwadratowy 4 zamknij nawias kwadratowy otwórz nawias kwadratowy 4 zamknij nawias kwadratowy znak równości 6 średnik prawy ukośnik prawy ukośnik przypisanie wartości 6 do elementu tablicy o indeksach otwórz nawias okrągły 4 przecinek 4 zamknij nawias okrągły. Linia 15. cout otwórz nawias ostrokątny otwórz nawias ostrokątny tablica otwórz nawias kwadratowy 4 zamknij nawias kwadratowy otwórz nawias kwadratowy 4 zamknij nawias kwadratowy średnik prawy ukośnik prawy ukośnik wynik znak równości 6. Linia 17. prawy ukośnik prawy ukośnik Zwolnienie pamięci zaalokowanej dla tablicy dynamicznej. Linia 18. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny n średnik i plus plus zamknij nawias okrągły. Linia 19. otwórz nawias klamrowy. Linia 20. delete otwórz nawias kwadratowy zamknij nawias kwadratowy tablica otwórz nawias kwadratowy i zamknij nawias kwadratowy średnik. Linia 21. zamknij nawias klamrowy. Linia 22. delete otwórz nawias kwadratowy zamknij nawias kwadratowy tablica średnik. Linia 24. return 0 średnik. Linia 25. zamknij nawias klamrowy.

Wynik działania programu:

Linia 1. 6.

Obiekty dynamiczne

Podobnie do tego, jak tworzymy zmienne dynamiczne, możemy tworzyć obiekty dynamiczne. Obiekty dynamiczne w języku C++ to instancje klas lub struktury, które są alokowane w czasie wykonywania programu na stercie zamiast na stosie.

Alokacja obiektów dynamicznych odbywa się za pomocą operatora new, który rezerwuje odpowiednią ilość pamięci na stercie dla obiektu danej klasy i zwraca wskaźnik do tego obszaru pamięci. Aby zwolnić zajmowaną pamięć, kiedy obiekt nie jest już potrzebny, używa się operatora delete. Dzięki temu mechanizmowi, programista ma kontrolę nad cyklem życia obiektów.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 5. class Samochod otwórz nawias klamrowy. Linia 6. private dwukropek. Linia 7. string marka średnik. Linia 8. public dwukropek. Linia 9. Samochod otwórz nawias okrągły string m zamknij nawias okrągły dwukropek marka otwórz nawias okrągły m zamknij nawias okrągły otwórz nawias klamrowy zamknij nawias klamrowy prawy ukośnik prawy ukośnik Konstruktor. Linia 10. void pokaz otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. cout otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów Marka samochodu dwukropek cudzysłów otwórz nawias ostrokątny otwórz nawias ostrokątny marka otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 12. zamknij nawias klamrowy. Linia 13. tylda Samochod otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy zamknij nawias klamrowy. Linia 14. zamknij nawias klamrowy średnik. Linia 16. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 17. Samochod asterysk mojSamochod znak równości new Samochod otwórz nawias okrągły cudzysłów Toyota cudzysłów zamknij nawias okrągły średnik prawy ukośnik prawy ukośnik Alokacja dynamiczna. Linia 18. mojSamochod minus zamknij nawias ostrokątny pokaz otwórz nawias okrągły zamknij nawias okrągły średnik prawy ukośnik prawy ukośnik Dostęp do metody przez wskaźnik. Linia 20. delete mojSamochod średnik prawy ukośnik prawy ukośnik Zwolnienie pamięci. Linia 21. return 0 średnik. Linia 22. zamknij nawias klamrowy.

W tym przykładzie tworzymy dynamicznie obiekt klasy Samochod na stercie za pomocą operatora new. Obiekt jest dostępny przez wskaźnik mojSamochod, a jego metody są dostępne za pomocą operatora strzałki (->). Pola są prywatne. Po zakończeniu używania obiektu, zwalniamy pamięć za pomocą delete.

Dlaczego używamy obiektów dynamicznych? Obiekty dynamiczne mogą być tworzone i usuwane w dowolnym momencie działania programu, co daje możliwość kontroli nad zarządzaniem pamięcią.

W przypadku dużych obiektów lub zmiennej liczby obiektów, alokacja dynamiczna pozwala na efektywniejsze wykorzystanie zasobów.

Obiekty dynamiczne istnieją aż do momentu ich jawnego usunięcia, co oznacza, że mogą przetrwać dłużej niż zasięg bloku kodu, w którym zostały utworzone.

Jakie jest ryzyko?

Programista jest odpowiedzialny za zwolnienie pamięci zajmowanej przez obiekty dynamiczne, co może prowadzić do wycieków pamięci, jeśli zostanie to zaniedbane.

Alokacja i dealokacja na stercie są zazwyczaj kosztowniejsze pod względem wydajności niż operacje na stosie.

Podsumowując, obiekty dynamiczne w C++ oferują dużą elastyczność i kontrolę nad zarządzaniem pamięcią, ale wymagają od programistów staranności w zarządzaniu cyklem życia obiektów i pamięcią, aby unikać wycieków i innych problemów.

Przykład 1

Pomimo że rozmiar dynamicznych tablic jest stały aż do momentu ich zwolnienia, istnieje prosta metoda na zarządzanie ich wielkością. Załóżmy, że mamy wskaźnik tab wskazujący na w pełni zapełnioną tablicę. W tej sytuacji możemy stworzyć nową tablicę nowa_tab. Będzie miała dwa razy większy rozmiar. Następnym krokiem jest przekopiowanie wszystkich elementów ze starej tablicy tab do nowej  tablicy nowa_tab. Po skopiowaniu danych zwalniamy pamięć przydzieloną dla starej tablicy za pomocą delete[] tab. Na koniec wskaźnik tab ustawiamy na nową tablicę nowa_tab.

Dzięki temu procesowi rozszerzamy dostępną przestrzeń tablicy, zachowując dotychczasowe dane i umożliwiając dodawanie nowych elementów

W ten sposób działa między innymi kontener vector ze standardowej biblioteki szablonów STL. Poniżej znajduje się implementacja uproszczonej klasy wektora inspirowana klasą vector z biblioteki STL.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 2. using namespace std średnik. Linia 4. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. prawy ukośnik prawy ukośnik Załóżmy przecinek że mamy dynamicznie alokowaną tablicę tab o określonym rozmiarze. Linia 6. int rozmiar znak równości 10 średnik. Linia 7. int asterysk tab znak równości new int otwórz nawias kwadratowy rozmiar zamknij nawias kwadratowy średnik. Linia 9. prawy ukośnik prawy ukośnik Wypełniamy tablicę danymi. Linia 10. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny rozmiar średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości i średnik. Linia 12. zamknij nawias klamrowy. Linia 14. prawy ukośnik prawy ukośnik Jeśli potrzebujemy więcej miejsca przecinek tworzymy nową przecinek większą tablicę. Linia 15. int nowyRozmiar znak równości 2 asterysk rozmiar średnik. Linia 16. int asterysk nowa podkreślnik tab znak równości new int otwórz nawias kwadratowy nowyRozmiar zamknij nawias kwadratowy średnik. Linia 18. prawy ukośnik prawy ukośnik Przekopiowujemy dane ze starej tablicy do nowej. Linia 19. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny rozmiar średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 20. nowa podkreślnik tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości tab otwórz nawias kwadratowy i zamknij nawias kwadratowy średnik. Linia 21. zamknij nawias klamrowy. Linia 23. prawy ukośnik prawy ukośnik Zwalniamy pamięć starej tablicy. Linia 24. delete otwórz nawias kwadratowy zamknij nawias kwadratowy tab średnik. Linia 25. prawy ukośnik prawy ukośnik Ustawiamy wskaźnik tab na nową tablicę. Linia 26. tab znak równości nowa podkreślnik tab średnik. Linia 27. prawy ukośnik prawy ukośnik Wskaźnik tab wskazuje na nową przecinek większą tablicę. Linia 29. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny rozmiar średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 30. cout otwórz nawias ostrokątny otwórz nawias ostrokątny nowa podkreślnik tab otwórz nawias kwadratowy i zamknij nawias kwadratowy otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów cudzysłów średnik. Linia 31. zamknij nawias klamrowy. Linia 32. cout otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 34. for otwórz nawias okrągły int i znak równości 0 średnik i otwórz nawias ostrokątny nowyRozmiar średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 35. cout otwórz nawias ostrokątny otwórz nawias ostrokątny nowa podkreślnik tab otwórz nawias kwadratowy i zamknij nawias kwadratowy otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów cudzysłów średnik. Linia 36. zamknij nawias klamrowy. Linia 37. cout otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 39. delete otwórz nawias kwadratowy zamknij nawias kwadratowy tab średnik prawy ukośnik prawy ukośnik Zwolnienie pamięci. Linia 41. return 0 średnik. Linia 42. zamknij nawias klamrowy.

Wynik działania programu:

Linia 1. prawy ukośnik prawy ukośnik Wypisujemy pierwsze 10 elementów nowej tablicy otwórz nawias okrągły dane przekopiowane zamknij nawias okrągły. Linia 2. 0 1 2 3 4 5 6 7 8 9. Linia 4. prawy ukośnik prawy ukośnik Wypisujemy całą nową tablicę przecinek włącznie z niezainicjowanymi elementami. Linia 5. 0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 0 0 0 0.

Pierwszy wiersz wyników wypisuje zawartość starej tablicy tab, ale po przekopiowaniu danych i przestawieniu wskaźnika tab na nową tablicę, jednak w rzeczywistości wypisuje pierwsze 10 elementów nowej tablicy nowa_tab. To pokazuje, że dane zostały pomyślnie przekopiowane z oryginalnej tablicy do nowej, większej tablicy.

Drugi wiersz wyników wypisuje zawartość nowej, większej tablicy nowa_tab po zmianie wskaźnika. Możemy zauważyć, że pierwsze 10 elementów zawiera przekopiowane wartości, a pozostałe 10 elementów zawiera wartości domyślne dla typu intC++, czyli 0. Jest to spowodowane tym, że nowo alokowana pamięć dla typów prostych (do których należy int) nie jest inicjowana żadnymi konkretnymi wartościami, chyba że zostanie to zrobione jawnie. W tym przypadku, ponieważ dodatkowe miejsce nie zostało wypełnione żadnymi danymi, domyślnie przyjmuje wartość 0.

Słownik

adres pamięci
adres pamięci

alias pamięci w języku C++ odnosi się do sytuacji, w której jednemu identyfikatorowi przypisuje się inną nazwę dla istniejącej wartości w pamięci; zmienna referencyjna jest przykładem aliasu pamięci – jedna zmienna może być dostępna poprzez różne identyfikatory; jeśli utworzymy zmienną referencyjną, tworzymy alias pamięci dla istniejącej zmiennej, co umożliwia dostęp do tej zmiennej za pomocą dwóch różnych nazw

LIFO (ang. Last In, First Out)
LIFO (ang. Last In, First Out)

koncepcja dynamicznych struktur danych, w których ostatni element, który został dodany, jest także pierwszym elementem, który zostaje usunięty; ten sposób zachowania jest charakterystyczny dla takich struktur danych jak stos (stack) czy kolejka LIFO (queue)

tablica alokowana dynamiczna
tablica alokowana dynamiczna

tablica, której rozmiar nie jest znany w momencie kompilacji, lecz określany w trakcie działania programu; taka tablica umożliwia nie tylko dodawanie do niej kolejnych elementów, ale także ich usuwanie; tablice dynamiczne mogą być również całkowicie usunięte przez programistę w celu zwolnienia zarezerwowanej dla nich pamięci (podobnie jak wskaźniki umożliwiają np. efektywniejsze wykorzystanie pamięci)

wskaźnik
wskaźnik

specjalna zmienna przeznaczona do przechowywania zawartego w pamięci adresu innej zmiennej; sam wskaźnik również przechowywany jest pod określonym innym adresem – jego wykorzystanie w programach może gwarantować liczne korzyści, np. efektywniejsze wykorzystanie zasobów pamięci

zmienna referencyjna
zmienna referencyjna

zmienna referencyjna w języku C++ to specjalny rodzaj zmiennej, która przechowuje referencję (alias) do innego obiektu lub zmiennej; jest to alias dla istniejącej wartości w pamięci, który umożliwia bezpośredni dostęp do tej wartości za pomocą różnej nazwy; przykłady zmiennych referencyjnych obejmują referencje do zmiennych typu prostego oraz referencje do obiektów klasowych.

Linia 1. int liczba znak równości 7 średnik prawy ukośnik prawy ukośnik Pierwotna zmienna. Linia 2. int ampersant ref podkreślnik liczba znak równości liczba średnik prawy ukośnik prawy ukośnik Zmienna referencyjna.
zmienna typu prymitywnego
zmienna typu prymitywnego

zmienna typu prymitywnego przechowuje w pamięci konkretne wartości; przykład:

Linia 1. int liczba znak równości 7 średnik.

wartości te nie są obiektami