Вопрос действительно соответствует названию: мне любопытно узнать, какова техническая причина этой разницы, но также и обоснование?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
Вопрос действительно соответствует названию: мне любопытно узнать, какова техническая причина этой разницы, но также и обоснование?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
Это потому, что 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*
, но при этом он может правильно удалять объекты при уничтожении , правильно вызывая их деструктор. Хорошо. Это все о том, как работает std::shared_ptr
.
Теперь вопрос в том, может ли std::unique_ptr
хранить объекты как void*
? Ну, ответ - да - при условии, что вы передадите подходящий аргумент в качестве аргумента. Вот одна из таких демонстраций:
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
Вы задали очень интересный вопрос в комментарии:
В моем случае мне понадобится стирающее стирание типа, но это также кажется возможным (за счет некоторого выделения кучи). По сути, означает ли это, что на самом деле есть ниша для интеллектуального указателя 3-го типа: интеллектуального указателя с исключительным владением и стирания типа.
на который @Steve Jessop предложил следующее решение,
На самом деле я никогда не пробовал этого, но, возможно, вы могли бы добиться этого, используя соответствующую
std::function
в качестве типа удалителя сunique_ptr
? Предположим, что это действительно работает, то все готово, исключительное право собственности и удаленный тип.
Следуя этому предложению, я реализовал это,
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;
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.
Надеюсь, это поможет.
Одно из обоснований заключается в одном из многих случаев использования shared_ptr
, а именно в качестве индикатора жизни или часового.
Это было упомянуто в оригинальной документации по ускорению:
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 теперь может быть блаженно не осведомлен о типе объекта, на который он обращается.
Эта компоновка освобождает подписчиков к эмитенту события обязанности обрабатывать случаи пересечения (что, если обратный вызов в очереди, ожидающий действия при активном_объекте уходит?), а также означает, что нет необходимости синхронизировать отмену подписки, weak_ptr<void>::lock
- синхронизированная операция.