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

Как запретить создание объекта на куче?

2.0 Middle🔥 131 комментариев
#Язык C++#Умные указатели и управление памятью

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

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

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

Запрет создания объектов на куче

В C++ есть несколько способов запретить динамическое выделение памяти (создание объектов на куче с помощью new).

Способ 1: Удалить operator new

Самый простой и надежный способ — явно удалить operator new:

class StackOnly {
public:
    StackOnly() = default;
    ~StackOnly() = default;
    
    // Удаляем operator new
    void* operator new(size_t) = delete;
    void* operator new[](size_t) = delete;
    void operator delete(void*) = delete;
    void operator delete[](void*) = delete;
};

int main() {
    StackOnly obj1;           // OK
    StackOnly* obj2 = new StackOnly();  // Ошибка компиляции!
    StackOnly arr[10];        // OK
    std::vector<StackOnly> v; // OK
}

Это работает потому что компилятор ищет глобальный operator new, но находит удаленную версию.

Способ 2: Приватный operator new (старый стиль)

Вариант для компиляторов без поддержки = delete:

class StackOnly {
pubate:
    // Приватные operator new
    void* operator new(size_t);
    void* operator new[](size_t);
    void operator delete(void*);
    void operator delete[](void*);

public:
    StackOnly() = default;
    ~StackOnly() = default;
};

int main() {
    StackOnly obj;             // OK
    StackOnly* ptr = new StackOnly();  // Ошибка компиляции!
}

Способ 3: Контроль с placement new

Если нужна более гибкая логика:

class StackOnly {
public:
    StackOnly() = default;
    ~StackOnly() = default;
    
    // Удаляем обычные operator new
    void* operator new(size_t) = delete;
    void* operator new[](size_t) = delete;
    
    // Разрешаем placement new (для специальных случаев)
    void* operator new(size_t, void* ptr) = default;
};

int main() {
    StackOnly obj;         // OK
    
    // new StackOnly();    // Ошибка!
    
    // Но placement new всё равно работает:
    char buffer[sizeof(StackOnly)];
    StackOnly* ptr = new (buffer) StackOnly();  // OK
}

Способ 4: CRTP (Curiously Recurring Template Pattern)

Для более универсального решения:

template<typename Derived>
class NonHeapAllocatable {
public:
    void* operator new(size_t) = delete;
    void* operator new[](size_t) = delete;
    void operator delete(void*) = delete;
    void operator delete[](void*) = delete;

private:
    ~NonHeapAllocatable() {}  // Protected для безопасности
    friend Derived;
};

class MyClass : public NonHeapAllocatable<MyClass> {
public:
    MyClass() = default;
    ~MyClass() = default;
};

int main() {
    MyClass obj;                    // OK
    MyClass* ptr = new MyClass();   // Ошибка компиляции!
}

Практический пример: RAII с гарантией stack allocation

class DatabaseConnection : public NonHeapAllocatable<DatabaseConnection> {
private:
    std::string host;
    int port;
    std::unique_ptr<Connection> conn;

public:
    DatabaseConnection(const std::string& h, int p)
        : host(h), port(p) {
        conn = std::make_unique<Connection>(host, port);
    }
    
    ~DatabaseConnection() {
        if (conn) {
            conn->close();
        }
    }
    
    void execute(const std::string& query) {
        if (!conn) throw std::runtime_error("Not connected");
        conn->execute(query);
    }
};

int main() {
    // Правильное использование (на стеке)
    DatabaseConnection db("localhost", 5432);
    db.execute("SELECT * FROM users");
    // Автоматическая очистка при выходе из области видимости
    
    // Это НЕ скомпилируется:
    // DatabaseConnection* badPtr = new DatabaseConnection("localhost", 5432);
}

Почему это полезно

1. RAII гарантия

Объекты на стеке гарантированно вызовут деструктор при выходе из области видимости:

void process() {
    MyResource res;  // Выделение
    // ... использование res ...
    // Автоматический вызов деструктора
}

2. Exception safety

Исключения не приведут к утечкам памяти:

void riskyOperation() {
    MyResource res;  // На стеке
    
    try {
        throwException();  // Может выбросить
    } catch (...) {
        // Деструктор res вызовется автоматически!
        throw;
    }
}

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

Стек обычно быстрее кучи (heap allocation):

// Куча: требует глобальной синхронизации allocator'а
for (int i = 0; i < 1000000; i++) {
    auto ptr = std::make_unique<MyClass>();  // Медленно
}

// Стек: просто инкремент указателя стека
for (int i = 0; i < 1000000; i++) {
    MyClass obj;  // Быстро
}

Проверка на компиляторе

Этот код вызовет ошибку компиляции:

class StackOnly {
public:
    void* operator new(size_t) = delete;
};

int main() {
    auto ptr = new StackOnly();  // error: use of deleted function
}

Ошибка будет похожа на:

error: use of deleted function 'void* StackOnly::operator new(size_t)'

Вывод

Запрет создания на куче — это важный инструмент для:

  1. Обеспечения RAII гарантии
  2. Предотвращения утечек памяти
  3. Гарантии exception safety
  4. Улучшения производительности

Используй = delete как стандартный способ в современном C++ (C++11 и выше).

Как запретить создание объекта на куче? | PrepBro