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

std::shared_ptr sharedToVoid; // legal;
std::unique_ptr uniqueToVoid; // ill-formed;

Ответы (2)

Это потому, что std :: shared_ptr реализует стирание типа, а std :: unique_ptr - нет.


Поскольку std :: shared_ptr реализует стирание типа, он также поддерживает другое интересное свойство, а именно. не требуется тип удалителя в качестве аргумента типа шаблона для шаблона класса. Посмотрите их декларации:

template > 
class unique_ptr;

, который имеет Deleter в качестве параметра типа, а

template 
class shared_ptr;

его нет.

Итак, почему shared_ptr реализует стирание типа?

Ну, он делает это, потому что он должен поддерживать подсчет ссылок, и для поддержки этого он должен выделять память из кучи, а поскольку должен выделять память в любом случае, он идет на один шаг дальше и реализует стирание типов, что тоже требует выделения кучи.Так что, по сути, это просто конъюнктурность!

Из-за стирания типа std :: shared_ptr может поддерживать две вещи:

  • Он может хранить объекты любого типа как void *, , но по-прежнему может удалять объекты при уничтожении , правильно вызывая их деструктор.
  • Тип удалителя не передается как аргумент типа в шаблон класса, что означает некоторую свободу без ущерба для безопасности типов.

Хорошо. Вот и все о том, как работает std :: shared_ptr.

Теперь вопрос в том, может ли std :: unique_ptr хранить объекты как void *? Что ж, ответ: да - при условии, что вы передадите подходящий удалитель в качестве аргумента. Вот одна из таких демонстраций:

int main()
{
    auto deleter = [](void const * data ) {
        int const * p = static_cast(data);
        std::cout << *p << " located at " << p <<  " is being deleted";
        delete p;
    };
    
    std::unique_ptr p(new int(959), deleter);
    
} //p will be deleted here, both p ;-)

Вывод (онлайн-демонстрация):

959 located at 0x18aec20 is being deleted

В комментарии вы задали очень интересный вопрос:

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

на который @ Steve Jessop предложил следующее решение:

Я никогда не пробовал этого, но, возможно, вы могли бы добиться этого, используя соответствующий std :: function в качестве типа удаления с unique_ptr? Предположим, что это действительно работает, тогда у вас все готово, исключительное право собственности и удалитель со стиранием типа.

Следуя этому предложению, я реализовал следующее (хотя он не использует std :: function, поскольку это не кажется необходимым):

using unique_void_ptr = std::unique_ptr;

template
auto unique_void(T * ptr) -> unique_void_ptr
{
    return unique_void_ptr(ptr, [](void const * data) {
         T const * p = static_cast(data);
         std::cout << "{" << *p << "} located at [" << p <<  "] is being deleted.\n";
         delete p;
    });
}

int main()
{
    auto p1 = unique_void(new int(959));
    auto p2 = unique_void(new double(595.5));
    auto p3 = unique_void(new std::string("Hello World"));
}  

Вывод (онлайн-демонстрация):

{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.

Надеюсь, что это поможет.

Одно из объяснений заключается в одном из многих вариантов использования shared_ptr, а именно в качестве индикатора времени жизни или дозорного.

Это было упомянуто в исходной документации по бусту:

auto register_callback(std::function closure, std::shared_ptr pv)
{
    auto closure_target = { closure, std::weak_ptr(pv) };
    ...
    // store the target somewhere, and later....
}

void call_closure(closure_target target)
{
    // test whether target of the closure still exists
    auto lock = target.sentinel.lock();
    if (lock) {
        // if so, call the closure
        target.closure();
    }
}

Где closure_target примерно так:

struct closure_target {
    std::function closure;
    std::weak_ptr sentinel;
};

Вызывающий зарегистрирует обратный вызов примерно так:

struct active_object : std::enable_shared_from_this
{
    void start() {
      event_emitter_.register_callback([this] { this->on_callback(); }, 
                                       shared_from_this());
    }

    void on_callback()
    {
        // this is only ever called if we still exist 
    }
};

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

Эта договоренность освобождает подписчиков эмиттера событий от обязанности обрабатывать случаи пересечения (что, если обратный вызов находится в очереди, ожидая выполнения действий, пока active_object уйдет?), А также означает, что нет необходимости синхронизировать отписку .weak_ptr :: lock - синхронизированная операция.

2022 WebDevInsider