T * ptr = new T();
delete ptr;
delete
в качестве аргумента получает указатель на память, которую необходимо освободитьА как работает вызывать конструктор объекта в выделенной ОС памяти мне?
Оператор new
как и многие другие может быть переопределен.
void* operator new(std::size_t count, void* ptr);
static byte static_data[10000];
// ...
Type* foo = new (static_data) Type(42, "hello", "world");
В С++ определен, так называемый, placement new
, который НЕ выделяет память, а только создает объект в области памяти, которая передана в качестве аргумента.
Таким образом, можно конструировать объекты в известной области памяти. Эта память, в свою очередь, может быть выделена любым способом.
Ок, а какие еще есть переопределения new
?
Как известно, если опертатор new не смог выделить память, то он генерирует исключение std::bad_alloc
.
Функция malloc
не генерирует исключения, а возвращает NULL.
Иногда требуется, чтобы оператор new
не генерировал исключение в случае ошибки, а возвращал невалидный адрес (nullptr
). Для таких целей можно использовать переопределенный оператор new
с параметром std::nothrow
.
void* operator new(size_t size, const std::nothrow_t &nt);
Type* ptr = new(std::nothrow) Type(42, "hello", "world");
if(ptr != nullptr) {
// using ptr
// ...
}
Можно определять свои операторы new
void* operator new (size_t cnt, const std::string& s) {
std::cout << s << std::endl;
// просим у системы память размером cnt
return ::operator new(cnt);
}
Type* ptr = new (std::string("some debug message")) Type();
// ...
delete ptr;
С++ позволяет переопределять методы new
и delete
для классов.
struct Type {
static void* operator new(std::size_t size) {
void* p = ::operator new(size);
std::cout << "Type::new(" << size << ") " << p << std::endl;
return p;
}
static void operator delete(void* p) {
std::cout << "Type::delete(" << p << ")" << std::endl;
if (p == nullptr)
return;
::operator delete(p);
}
};
Type* ptr = new Type();
delete ptr;
Переопределенные операторы new
позволяют управлять памятью в вашем приложении на любом уровне и позволяют писать приложения под любые требования.
Но, к счастью (или сожалению), управлять памятью на таком уровне требуется не часто, только в очень специфичных условиях.
struct Block {
size_t size;
bool avaible;
};
Модель основана на массиве блоков памяти.
Выделение памяти сравнимо с задачей нахождения первого свободного блока.
Освобождение памяти: задача “удаление” элемента из массива.
В начале в памяти располагается только одна структура, размер которой равен всей свободной памяти.
По мере выделения памяти появляются новые блоки. Размер блоков определяется размером объектов распологаемых в блоках.
При новом запросе на выделение памяти последовательно проверяются все доступные блоки необходимого размера.
Освобождение блока памяти приводит к очередному поиску предыдущего свободного блока и изменению его размера.
Главный минус такой модели – скорость выделения и освобождения памяти: O(N), где N - количество блоков.
Но можно получить постоянное время освобождение памяти путем изменения служебной структуры.
struct MCB {
MCB* next;
};
Модель основана на списке из блоков памяти одинакового размера.
Выделение памяти: возвращаем первый элемент списка (а ля pop
).
Выделение памяти: возвращаем первый элемент списка (а ля pop
).
Освобождение памяти: добавляем в список участок памяти (а ля push
).
Освобождение памяти: добавляем в список участок памяти (а ля push
).
Главный плюс такой модели – константная скорость выделения и освобождения памяти.
Минус – фрагментация памяти. Участки памяти перемешиваются, расходуется памяти больше, чем необходимо под объект.
Чтобы уменьшить проблему с фрагментацией в описаной выше модели управления памятью, можно завести несколько подобных структур, с блоками разного размера.
Когда требуется выделить N байт, модель ищет экземпляр аллокатора c размером блока, больше или равным N.
Самый простой подход добавить поддержку многопоточности в аллокатор – это добавить mutex или любой другой механизм синхронизации потоков. Недостатки такого решения очевидны.
Чтобы как-то нивелировать недостатки, связанные с постоянной синхронизацией, создают локальный для каждого потока кэш.
Основная идея: для каждого потока есть свой стек свободных блоков. Если надо выделить блок, который находится в локальном стеке, то не требуется времени на синхронизацию между потоками.
Если локальный кэш переполняется, то идет обращение к глобальному стеку свободных блоков памяти.
* TC = Thread CachingСуществуют различные модели управления памятью, каждая из которых может хорошо работать в одних услосиях и плохо в других.
Выбор модели необходимо осуществлять в зависимости от задачи, требований и других условий.
В некоторых частях стандартной библиотеки языка С++ используются специальные объекты для выделения и освобождения памяти, которые называются аллокаторами.
STL аллокаторы используются как абстракция, преобразующая запросы на выделение памяти в физическую операцию её выделения.
Все стандартные контейнеры используют распределители памяти для выделения динамической памяти. Это позволяет гибко изменять политики выделения/освобождения памяти в стандартных контейнерах.
template <class T, class Alloc = allocator<T>>
class vector;
template <class T, class Alloc = allocator<T>>
class list;
template <class Key, class T,
class Hash = hash<Key>, class Pred = equal_to<Key>,
class Alloc = allocator<pair<const Key,T>> >
class unordered_map;
allocate(size_t N)
– выделяет память для N элементов (n * sizeof(T)
)construct(void* p, Args&&... args)
– инициализирует элемент по адресу p
, используя аргументы args
destroy(void *p)
– уничтожает элемент по адресу p
deallocate(void* p, size_t N)
– освобождает память по адресу p
в которой располагается N элементов.Начиная с С++11 для создания собственного аллокатора требуется определить только функции allocate
и deallocate
.
По умолчанию, функция construct
использует placement new
, а destroy
явно вызовает деструктор.
STL аллокаторы являются еще абстракцией в библиотеке STL, которая помогает использовать различные модели управления памятью в ваших приложениях без переписывания большой части кода.
Так же аллокаторы могут быть полезны при профилировании вашего приложения.