programowanieProgramista C++, programista systemowy

Opowiedz o mechanizmie operatora new/operatora delete w C++. Jak różnią się od new/delete, kiedy i dlaczego funkcje operatorów są przeciążane w klasach użytkownika?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W C++ operator new i operator delete to funkcje przydzielania i zwalniania pamięci, które są wywoływane podczas tworzenia i usuwania obiektów za pomocą operatorów new i delete. Domyślnie używają standardowego alokatora, ale w klasie można je przeciążyć, aby uzyskać bardziej precyzyjną kontrolę nad przydzielaniem pamięci.

  • operator new przydziela "surowy" obszar pamięci, nie wywołując konstruktora.
  • Po przydzieleniu pamięci automatycznie wywoływany jest konstruktor obiektu.
  • operator delete zwalnia pamięć po zakończeniu destruktora obiektu.

Użycie (wersje globalne i lokalne):

  • Wersja globalna jest stosowana domyślnie (np. przy new int).
  • Przeciążając specyficzny operator new w klasie, można zoptymalizować działanie z pamięcią dla obiektów tej klasy (np. pula obiektów, śledzenie, ponowne użycie bloków).

Przykład przeciążenia operatora new/operatora delete:

#include <iostream> class TrackAlloc { public: void* operator new(size_t size) { std::cout << "TrackAlloc::new dla " << size << " bajtów "; return ::operator new(size); } void operator delete(void* ptr) { std::cout << "TrackAlloc::delete "; ::operator delete(ptr); } };

Niuanse:

  • Wywołanie nowych/usunięcia obiektów z argumentami (placement new) wymaga osobnego przeciążenia.
  • Przeciążone operatory są wywoływane tylko przy tworzeniu/usuwaniu obiektów klasy, a nie dla new[]/delete[]. Dla nich można przeciążyć osobne operator new[]/delete[].
  • Manipulacje pamięcią są niebezpieczne — wymagana jest prawidłowa obsługa wyjątków.

Pytanie podchwytliwe.

"Co się stanie, jeśli przeciążę operator new w klasie, a następnie stworzę obiekt przez zmienną odziedziczonej klasy? Która wersja operatora new zostanie wywołana?"

Odpowiedź: Zostanie wywołany operator new klasy, której imienia używa się do tworzenia obiektu. Jeśli klasa pochodna nie ma zaimplementowanego operatora new, nastąpi próba znalezienia odpowiedniej wersji w klasie bazowej lub wersji globalnej.

Przykład:

struct Base { void* operator new(size_t s) { std::cout << "Base new "; return ::operator new(s); } }; struct Derived : Base {}; Derived* p = new Derived; // Wywoła Base::operator new!

Przykłady rzeczywistych błędów z powodu nieznajomości niuansów tematu.


Historia

Programiści przeciążyli operator new/delete bez wsparcia dla prawidłowej obsługi wyjątków. Przy wyrzuceniu wyjątku w konstruktorze pamięć nie była zwalniana, co prowadziło do wycieków pamięci.


Historia

Niepoprawnie zaimplementowano operator new[] i operator delete[]: dla klasy, w której zawarte były tablice, nowa implementacja nie była wywoływana — używane były domyślne wersje, co prowadziło do niesynchronizacji logiki przydzielania i zwalniania pamięci.


Historia

Przeciążenie globalnego operatora new wpłynęło na działanie zewnętrznych bibliotek: wszystkie obiekty (w tym tymczasowe i z STL) zaczęły być alokowane przez alokator z logowaniem, co krytycznie spowolniło działanie rdzenia aplikacji.