Pytanie naprawdę pasuje do tytułu: jestem ciekawy, jaki jest techniczny powód tej różnicy, ale także uzasadnienie?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
Odpowiedzi:
Dzieje się tak, ponieważ std::shared_ptr
implementuje wymazywanie typów, a std::unique_ptr
nie.
Ponieważ std::shared_ptr
implementuje wymazywanie typów, obsługuje również inną interesującą właściwość, a mianowicie. to jednak nie potrzebują typ Deleter jako argumentu typu szablonu do szablonu klasy. Spójrz na ich deklaracje:
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;
który ma Deleter
jako parametr typu, while
template<class T>
class shared_ptr;
nie ma tego.
Teraz pytanie brzmi, dlaczego shared_ptr
implementuje wymazywanie typów? Cóż, robi to, ponieważ musi obsługiwać liczenie odwołań, a aby to obsługiwać, musi przydzielać pamięć ze sterty, a ponieważ i tak musi alokować pamięć, idzie o krok dalej i implementuje wymazywanie typów - co wymaga sterty alokacja też. Więc w zasadzie to po prostu bycie oportunistą!
Ze względu na wymazywanie typów std::shared_ptr
jest w stanie obsługiwać dwie rzeczy:
void*
, ale nadal jest w stanie prawidłowo usuwać obiekty po zniszczeniu , poprawnie wywołując ich destruktor .W porządku. To wszystko o tym, jak std::shared_ptr
działa.
Teraz pytanie brzmi, czy można std::unique_ptr
przechowywać obiekty jako void*
? Cóż, odpowiedź brzmi: tak - pod warunkiem, że jako argument przekażesz odpowiedni element usuwający. Oto jedna taka demonstracja:
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 ;-)
Wyjście ( demo online ):
959 located at 0x18aec20 is being deleted
W komentarzu zadałeś bardzo interesujące pytanie:
W moim przypadku będę potrzebował kasownika usuwającego typ, ale wydaje się to również możliwe (kosztem alokacji sterty). Zasadniczo, czy to oznacza, że faktycznie istnieje nisza dla trzeciego typu inteligentnego wskaźnika: inteligentnego wskaźnika posiadającego wyłączną własność z usuwaniem typu.
do którego @Steve Jessop zasugerował następujące rozwiązanie,
Nigdy tego nie próbowałem, ale może można to osiągnąć, używając odpowiedniego typu
std::function
jako deleter zunique_ptr
? Przypuśćmy, że to faktycznie działa, więc gotowe, wyłączna własność i kasownik z usuniętym typem.
Idąc za tą sugestią, zaimplementowałem to (choć nie wykorzystuje, std::function
bo nie wydaje się to konieczne):
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;
template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
return unique_void_ptr(ptr, [](void const * data) {
T const * p = static_cast<T const*>(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"));
}
Wyjście ( demo online ):
{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.
Mam nadzieję, że to pomoże.
std::function
typu usuwania z unique_ptr
? Przypuśćmy, że to faktycznie działa, więc gotowe, wyłączna własność i kasownik z usuniętym typem.
Jednym z powodów jest jeden z wielu przypadków użycia a shared_ptr
- mianowicie jako wskaźnik czasu życia lub wartownik.
Wspomniano o tym w oryginalnej dokumentacji doładowania:
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();
}
}
Gdzie closure_target
jest coś takiego:
struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};
Dzwoniący zarejestrowałby wywołanie zwrotne w następujący sposób:
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
}
};
ponieważ shared_ptr<X>
jest zawsze konwertowalny na shared_ptr<void>
, event_emitter może być teraz błogo nieświadomy typu obiektu, do którego przywołuje.
Takie rozwiązanie zwalnia abonentów emitera zdarzeń z obowiązku obsługi przypadków przekroczenia (a co, jeśli callback w kolejce, czekając na akcję, podczas gdy obiekt active_object odchodzi?), A także oznacza, że nie ma potrzeby synchronizowania wypisania. weak_ptr<void>::lock
jest operacją zsynchronizowaną.
std::unique_ptr<void, D>
jest nadal możliwe, podając odpowiedni plikD
.