← Назад к вопросам

Как работает new?

1.7 Middle🔥 181 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает оператор new в C++

Оператор new — это фундаментальная операция для динамического выделения памяти на heap. Он состоит из трёх основных этапов: выделение памяти, вызов конструктора и возврат указателя.

Базовый синтаксис

int* ptr = new int;           // Выделить памяти для int
int* arr = new int[100];      // Выделить массив из 100 int
MyClass* obj = new MyClass(); // Выделить и инициализировать
MyClass* obj2 = new MyClass(10, 20);  // С параметрами

Три шага работы new

Шаг 1: Выделение памяти (malloc или эквивалент)

int* ptr = new int;
// 1. Выделяется 4 или 8 байт на heap
malloc(sizeof(int))

Шаг 2: Вызов конструктора (если это объект)

MyClass* obj = new MyClass(42);
// 2. Конструктор MyClass::MyClass(42) вызывается
// на адресе выделенной памяти

Шаг 3: Возврат указателя

// 3. Возвращается указатель на выделенную память

Как это выглядит под капотом

// Упрощённо, new работает примерно так:

template<typename T>
T* operator_new()
{
    // 1. Запросить память
    void* raw_memory = malloc(sizeof(T));
    
    if (!raw_memory) {
        throw std::bad_alloc();  // Если не хватает памяти
    }
    
    // 2. Вызвать конструктор (placement new)
    T* object = new (raw_memory) T();
    
    // 3. Вернуть указатель
    return object;
}

// Вызов:
int* ptr = operator_new<int>();  // Эквивалент: new int

Выделение памяти для примитивов

int* ptr = new int;           // Выделить, не инициализировать
int* ptr = new int();         // Выделить, инициализировать нулём
int* ptr = new int(42);       // Выделить, инициализировать 42

float* f = new float{3.14};   // C++11 uniform initialization

Выделение памяти для объектов

class MyClass {
public:
    int x;
    std::string name;
    
    MyClass() : x(0), name("") {
        std::cout << "Default constructor" << std::endl;
    }
    
    MyClass(int x, std::string n) : x(x), name(n) {
        std::cout << "Parameterized constructor" << std::endl;
    }
    
    ~MyClass() {
        std::cout << "Destructor" << std::endl;
    }
};

int main() {
    MyClass* obj1 = new MyClass();          // Default constructor
    MyClass* obj2 = new MyClass(42, "Alice");  // Parameterized
    
    delete obj1;  // Вызывает деструктор
    delete obj2;  // Вызывает деструктор
}

Выделение массивов

int* arr = new int[100];  // Массив из 100 int

// Важно: нужно удалять с []
delete[] arr;  // Правильно
// delete arr;  // НЕПРАВИЛЬНО! Memory leak

// Для объектов:
MyClass* objects = new MyClass[50];
delete[] objects;  // Вызывает деструктор для всех 50 объектов

Где располагается память

int local = 5;           // Stack (автоматическое удаление)
int* heap = new int(5);  // Heap (ручное удаление)

static int global = 5;   // Global (удаляется при выходе из программы)

Stack vs Heap:

Stack:                          Heap:
- Быстро                       - Медленнее
- Ограниченный размер (~1-8MB)  - Большой размер (ГБ)
- Автоматическое удаление     - Ручное удаление
- Жёсткая структура (LIFO)     - Гибкая структура

Обработка ошибок

// Если памяти не хватает, new выбросит исключение
try {
    int* arr = new int[1000000000];  // Слишком много
} catch (std::bad_alloc& e) {
    std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}

// Старый style (C-style):
int* ptr = new (nothrow) int[1000000000];
if (ptr == nullptr) {
    std::cerr << "Allocation failed" << std::endl;
}

Правильное использование: RAII

// ПЛОХО: ручное управление памятью
void function() {
    MyClass* obj = new MyClass();
    // Если это выброшет исключение, memory leak!
    if (something_wrong) throw std::runtime_error("Error");
    delete obj;  // Может не выполниться
}

// ХОРОШО: умные указатели (RAII)
void function() {
    std::unique_ptr<MyClass> obj(new MyClass());
    if (something_wrong) throw std::runtime_error("Error");
    // obj автоматически удалится при выходе из scope
}

// ЕЩЁ ЛУЧШЕ: make_unique
void function() {
    auto obj = std::make_unique<MyClass>();
    // obj удалится автоматически
}

Перегрузка operator new

class MyClass {
public:
    // Перегружаем new для класса
    void* operator new(size_t size) {
        std::cout << "Custom new: " << size << " bytes" << std::endl;
        return malloc(size);
    }
    
    void operator delete(void* ptr) {
        std::cout << "Custom delete" << std::endl;
        free(ptr);
    }
};

int main() {
    MyClass* obj = new MyClass();  // Вызывает custom new
    delete obj;                    // Вызывает custom delete
}

Placement new (продвинутая техника)

// Конструктор вызывается на уже выделенной памяти
char buffer[sizeof(MyClass)];
MyClass* obj = new (buffer) MyClass(42);  // Placement new

// Объект создан на stack-выделенном буфере!
// Деструктор нужно вызвать вручную:
obj->~MyClass();
// Но НЕ delete obj (не выделял через new)

Производительность

// new + delete имеют overhead
int* ptr = new int;  // ~50-100 циклов на выделение
delete ptr;          // ~30-50 циклов на удаление

// Stack allocation значительно быстрее
int x = 5;  // ~1 цикл

// Для критичного по производительности кода:
std::vector<int> pool;  // Пул предвыделённой памяти
pool.resize(1000);
// Потом переиспользуем существующую память

Практический пример

#include <iostream>
#include <memory>

class Person {
public:
    std::string name;
    int age;
    
    Person(const std::string& n, int a) : name(n), age(a) {
        std::cout << "Person created: " << name << std::endl;
    }
    
    ~Person() {
        std::cout << "Person destroyed: " << name << std::endl;
    }
};

int main() {
    // OLD (manual management)
    Person* p1 = new Person("Alice", 30);
    // ...
    delete p1;  // Ручное удаление
    
    // NEW (RAII с unique_ptr)
    {
        auto p2 = std::make_unique<Person>("Bob", 25);
        // p2 удалится автоматически при выходе из scope
    }  // ~Person() вызывается здесь
    
    // NEW (RAII с shared_ptr для множественного владения)
    std::shared_ptr<Person> p3 = std::make_shared<Person>("Charlie", 35);
    {
        std::shared_ptr<Person> p4 = p3;  // Shared ownership
        // p3 и p4 указывают на один объект
    }  // p4 удаляется, но объект остаётся (p3 всё ещё владеет)
    
    return 0;
}  // p3 удаляется здесь

Итог

new работает в 3 шага:

  1. Выделить память на heap (malloc)
  2. Вызвать конструктор на выделенной памяти
  3. Вернуть указатель на объект

Важные правила:

  • new → delete (pairing)
  • new[] → delete[] (для массивов)
  • Используй умные указатели (unique_ptr, shared_ptr)
  • Избегай ручного управления памятью
  • Помни о RAII принципе

Современный C++ (C++11+) предпочитает умные указатели вместо ручного new/delete.