Как у unique_ptr не должно быть никаких накладных расходов, если ему нужно сохранить средство удаления? - PullRequest
0 голосов
/ 24 апреля 2018

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

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

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

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

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

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

Ответы [ 4 ]

0 голосов
/ 29 апреля 2018

Краткое вступление:

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

Вернуться к удалению:

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

Если средство удаления пустое (не имеет состояния), вам не нужно хранить его внутри unique_ptr. Если вам это нужно, вы можете просто построить его, когда вам это нужно. Все, что вам нужно знать, это тип удалителя (и это один из аргументов шаблона для 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();
}
0 голосов
/ 24 апреля 2018

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

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

0 голосов
/ 24 апреля 2018

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

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

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;

    // ...
};

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

0 голосов
/ 24 апреля 2018

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

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

...