Как unique_ptr не имеет накладных расходов, если он должен хранить делектор?

Сначала взгляните на то, что C++ сказал Primer об unique_ptr и shared_ptr:
$ 16.1.6. Эффективность и гибкость

Мы можем быть уверены, что shared_ptr не удерживает делетера в качестве прямого члена, потому что тип делетера неизвестен до времени выполнения.

Поскольку тип делетера является частью типа unique_ptr, тип элемента удаления известен во время компиляции. Делетер может храниться непосредственно в каждом объекте unique_ptr.

Таким образом, похоже, что shared_ptr не имеет прямого члена deleter, но unique_ptr делает. Тем не менее, в ответе на другой вопрос другого вопроса говорится:

Если вы предоставляете deleter в качестве аргумента шаблона (как в unique_ptr), он является частью этого типа, и вам не нужно хранить что-либо дополнительное в объектах этого типа. Если deleter передается как аргумент конструктора (как в shared_ptr), вам нужно сохранить его в объекте. Это стоимость дополнительной гибкости, так как вы можете использовать разные удалители для объектов того же типа.

Два приведенных параграфа полностью противоречат друг другу, что меня смущает. Что еще, многие люди говорят, что unique_ptr является нулевым накладным, поскольку ему не нужно хранить делетера в качестве члена. Однако, как известно, unique_ptr имеет конструктор unique_ptr<obj,del> p(new obj,fcn), что означает, что мы можем передать ему deleter, поэтому unique_ptr видимому, хранит deleter как член. Какой беспорядок!

Ответ 1

std::unique_ptr<T> скорее всего, будет нулевым (с любой нормальной реализацией стандартной библиотеки). std::unique_ptr<T, D> для произвольного D не является вообще нулевым накладным.

Причина проста: оптимизация Empty-Base может быть использована для устранения хранилища дебетера в случае, если он является пустым (и, следовательно, без него) типом (например, std::default_delete).

Ответ 2

Ключевая фраза, которая, кажется, смущает вас, - "Удаленный пользователь может быть сохранен напрямую". Но нет смысла хранить делетиер типа std::default_delete. Если вам это нужно, вы можете просто создать его как std::default_delete{}.

В общем случае, без сохранения состояния без сохранения состояния, они не могут быть сохранены, поскольку вы можете создавать их по требованию.

Ответ 3

Анже ответ объяснил довольно тщательно, что происходит.

Для любопытных, как вещи могут выглядеть под обложками

template<typename T, typename D, bool Empty = std::is_empty_v<D>>
class unique_ptr
{
    T* ptr;
    D d;

    // ... 
};

template<typename T, typename D>
class unique_ptr<T, D, true> : D
{
    T* ptr;

    // ...
};

Который специализируется на пустых удалениях и использует пустую оптимизацию базы.

Ответ 4

Краткое введение:

unique_ptr может вводить некоторые небольшие накладные расходы, но не из-за делетера, а потому, что, когда вы переходите от него, значение должно быть установлено равным нулю, если, если вы используете исходные указатели, вы можете оставить старый указатель в подверженном ошибкам, но законном состоянии, где он все еще указывает где он указывал раньше. Разумеется, оптимизатор может оптимизировать, но он не гарантируется.

Назад к делетеру:

Другие ответы правильные, но продуманные. Итак, вот упрощенная версия без упоминания EBO или других сложных терминов.

Если пустытель пуст (не имеет состояния), вам не нужно держать его внутри unique_ptr. Если вам это нужно, вы можете просто построить его, когда вам это нужно. Все, что вам нужно знать, это тип deleter (и это один из аргументов шаблона для unique_ptr).

Для примера рассмотрим следующий код, который также демонстрирует простое создание по требованию объекта без гражданства.

#include <iostream>
#include <string>
#include <string_view>

template<typename Person>
struct Greeter{
    void greet(){
        static_assert(std::is_empty_v<Person>, "Person must be stateless");
        Person p; // Stateless Person instance constructed on demand
        std::cout << "Hello " << p() << std::endl;
    }
    // ... and not kept as a member.
};

struct Bjarne{
    std::string_view operator()(){
        return "Bjarne";
    }
};

int main() {
    Greeter<Bjarne> hello_bjarne;
    hello_bjarne.greet();
}