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

Какие плюсы и минусы make_unique?

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

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

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

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

Плюсы и минусы make_unique

make_unique — это функция-вспомогательная (helper function) введённая в C++14 для безопасного создания объектов с управлением памятью через unique_ptr. Она имеет важные преимущества и несколько ограничений.

Плюсы make_unique

1. Exception Safety (Исключительная безопасность)

Проблема с обычным конструктором:

// Опасно! Может произойти утечка
someFunction(unique_ptr<MyClass>(new MyClass()), expensiveOperation());

// Возможный порядок:
// 1. new MyClass() - создание объекта
// 2. expensiveOperation() - может выбросить исключение
// 3. unique_ptr<MyClass>(...) - обёртка
// 4. someFunction(...)

// Если на шаге 2 выбросится исключение,
// объект на шаге 1 будет утёкшим!

Решение с make_unique:

// Безопасно
someFunction(make_unique<MyClass>(), expensiveOperation());

// make_unique атомарно создаёт объект и оборачивает в unique_ptr
// Если expensiveOperation() выбросит исключение,
// unique_ptr всё ещё владеет объектом

2. Более читаемый код

// Плохо: повторение типа
unique_ptr<MyClass> ptr1(new MyClass(42));
unique_ptr<ComplexTemplate<int, string, double>> ptr2(
    new ComplexTemplate<int, string, double>(1, "hello", 3.14)
);

// Хорошо: компилятор выводит тип
auto ptr1 = make_unique<MyClass>(42);
auto ptr2 = make_unique<ComplexTemplate<int, string, double>>(
    1, "hello", 3.14
);

3. Меньше типов для запоминания

// Один синтаксис для всех случаев
make_unique<int>();
make_unique<string>("hello");
make_unique<vector<int>>(10, 5);  // vector из 10 элементов со значением 5
make_unique<MyClass>(arg1, arg2, arg3);

// Вместо трёх способов:
new int();
new string("hello");
new vector<int>(10, 5);

4. Единая аллокация

// Всё в одной аллокации памяти
auto ptr = make_unique<MyClass>(arg1, arg2);

// Сравните с двумя аллокациями при обёртывании:
unique_ptr<MyClass> ptr(new MyClass(arg1, arg2));

5. Правильное использование move-семантики

class Container {
private:
    unique_ptr<Resource> resource;
    
public:
    void init() {
        resource = make_unique<Resource>();  // RVO работает
    }
};

// Компилятор оптимизирует move в assignment

Минусы make_unique

1. Защищённые и приватные конструкторы

class MyClass {
protected:
    MyClass() {}  // Защищённый конструктор
};

// ОШИБКА: make_unique не может вызвать protected конструктор
auto ptr = make_unique<MyClass>();  // Не скомпилируется!

// Нужно использовать явный new (в исключительных случаях)
unique_ptr<MyClass> ptr(new MyClass());  // Работает

2. Custom deleter

// Удаление через custom функцию
auto deleter = [](FILE* f) { fclose(f); };

// make_unique не поддерживает это напрямую
// unique_ptr<FILE, decltype(deleter)> ptr(fopen(...), deleter);

// Приходится использовать new
unique_ptr<FILE, decltype(deleter)> ptr(
    fopen("data.txt", "r"),
    deleter
);

3. Массивы (до C++20)

// C++14, C++17: make_unique не работает с массивами
vector<int> arr = {1, 2, 3, 4, 5};

// Не работает:
// auto ptr = make_unique<int[]>(10);  // ОШИБКА

// Приходится писать:
unique_ptr<int[]> ptr(new int[10]);  // Или vector<int>

// C++20 и выше: поддержка массивов
auto arr = make_unique<int[]>(10);  // Работает!

4. Placement new (размещающий new)

// Если нужно разместить объект в конкретной памяти
char buffer[sizeof(MyClass)];

// make_unique не поддерживает
// auto ptr = make_unique<MyClass>(placement_ptr);

// Нужно использовать placement new
MyClass* ptr = new (buffer) MyClass();

5. Рекурсивные структуры данных

struct Node {
    int value;
    unique_ptr<Node> next;
};

// Сложность: нужно уметь выводить типы в сложных структурах
auto node = make_unique<Node>();
// node->next = make_unique<Node>();  // OK

Сравнение: make_unique vs new

// Вариант 1: new (опасно)
void example1(unique_ptr<A> a, unique_ptr<B> b) {}
example1(unique_ptr<A>(new A()), uniquePtr<B>(new B()));
// Может произойти утечка если конструктор B выбросит исключение

// Вариант 2: make_unique (безопасно)
void example2(unique_ptr<A> a, unique_ptr<B> b) {}
example2(make_unique<A>(), make_unique<B>());
// Exception safe

// Вариант 3: промежуточные переменные (безопасно, но многословно)
auto a = make_unique<A>();
auto b = make_unique<B>();
example1(move(a), move(b));

Performance considerations

// make_unique одна аллокация:
auto ptr = make_unique<MyClass>();

// Явный new + unique_ptr две операции:
unique_ptr<MyClass> ptr(new MyClass());

// Компилятор часто оптимизирует, но make_unique гарантирует

Best practices

// ВСЕГДА используйте make_unique вместо new
auto ptr1 = make_unique<MyClass>(arg1, arg2);

// Если нужен custom deleter:
unique_ptr<FILE, decltype(&fclose)> file(
    fopen("data.txt", "r"),
    &fclose
);

// Если нужен protected конструктор (редко):
class Helper : public MyClass {
public:
    static auto create() {
        return make_unique<Helper>();
    }
};

// Если нужны массивы (C++20+):
auto arr = make_unique<int[]>(10);

// Если нужен placement new:
alignas(MyClass) char buffer[sizeof(MyClass)];
auto ptr = new (buffer) MyClass();

Timeline улучшений

C++11: unique_ptr существует
C++14: Введена make_unique
C++17: Улучшена семантика
C++20: Поддержка массивов в make_unique

Заключение

make_unique — это правильный способ создания unique_ptr в современном C++. Его основное преимущество — исключительная безопасность в контексте вызова функций. Основные минусы (protected конструкторы, custom deleters) встречаются редко и легко обходятся. Рекомендация: всегда используйте make_unique, если нет веских причин не делать этого.

Какие плюсы и минусы make_unique? | PrepBro