Почему общий ptr является законным, а уникальный ptr плохо сформирован?
вопрос действительно вписывается в название: мне любопытно узнать, какова техническая причина этой разницы, но также и обоснование ?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
2 ответа:
потому что
std::shared_ptr
реализует тип-стирание, в то время какstd::unique_ptr
нет.
С
std::shared_ptr
реализует тип-стирание, он также поддерживает другое интересное свойство, а именно. это делает не нужен тип делетера как аргумент типа шаблона к шаблону класса. Посмотрите на их заявления:template<class T,class Deleter = std::default_delete<T> > class unique_ptr;
имеющего
Deleter
как параметр типа, в то время какtemplate<class T> class shared_ptr;
нет оно.
теперь вопрос в том, почему
shared_ptr
тип реализации-стирание? Ну, это так, потому что он должен поддерживать подсчет ссылок, и для поддержки этого он должен выделять память из кучи и так как это до выделить память в любом случае, он идет на один шаг дальше и реализует тип-стирание - который также нуждается в выделении кучи. Так что в основном это просто приспособленец.из-за стирания типа,
std::shared_ptr
способен поддерживать два вещи:
- он может хранить объекты любого типа как
void*
,тем не менее он по-прежнему может удалить объекты на уничтожение должным образом правильно вызов их деструкторов.- тип deleter не передается в качестве аргумента типа в шаблон класса, что означает немного свободы без ущерба для безопасности типа.
хорошо. Вот и все о том, как
std::shared_ptr
завод.теперь вопрос в том, может
std::unique_ptr
объекты хранения какvoid*
? Ну, ответ таков,да - при условии, что вы передаете подходящий deleter в качестве аргумента. Вот одна из таких демонстраций:int main() { auto deleter = [](void const * data ) { int const * p = static_cast<int const*>(data); std::cout << *p << " located at " << p << " is being deleted"; delete p; }; std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter); } //p will be deleted here, both p ;-)
выход (онлайн демо):
959 located at 0x18aec20 is being deleted
вы задали очень интересный вопрос в комментарии:
в моем случае мне понадобится тип стирания deleter, но это кажется возможным, как хорошо (в куче). В принципе, означает ли это, что на самом деле есть ниша для 3-го типа интеллектуального указателя: эксклюзивный интеллектуальный указатель с типом стирания.
, к которому @Steve Jessop предложил следующее решение,
я никогда не пробовал это, но, возможно, вы могли бы достичь этого с помощью соответствующего
std::function
как тип deleter сunique_ptr
? Предположим, что на самом деле работает тогда вы закончили, исключительное право собственности и стираемый тип deleter.следуя этому предложению, я реализовал это,
using deleter_t = std::function<void(void *)>; using unique_void_ptr = std::unique_ptr<void, deleter_t>; template<typename T> auto deleter(void const * data) -> void { T const * p = static_cast<T const*>(data); std::cout << "{" << *p << "} located at [" << p << "] is being deleted.\n"; delete p; } template<typename T> auto unique_void(T * ptr) -> unique_void_ptr { return unique_void_ptr(ptr, &deleter<T>); } 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.
надеюсь, что это поможет.
одно из объяснений находится в одном из многих случаев использования a
shared_ptr
- а именно как индикатор времени жизни или страж.это было упомянуто в оригинальной документации boost:
auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv) { auto closure_target = { closure, std::weak_ptr<void>(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<void()> closure; std::weak_ptr<void> sentinel; };
вызывающий зарегистрировал бы обратный вызов что-то вроде этого:
struct active_object : std::enable_shared_from_this<active_object> { 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<X>
всегда конвертируется вshared_ptr<void>
, event_emitter теперь может быть блаженно не знают о типе объекта он зовет обратно.эта договоренность освобождает подписчиков на эмитент событий обязательства обработки случаев пересечения (что делать, если обратный вызов в очереди, ожидая действия, пока active_object уходит?), а также означает, что нет необходимости синхронизировать отписку.
weak_ptr<void>::lock
- это синхронный режим работы.