2014-04-08 14 views
6

W ostatnim overload journal w temacie wprowadzająca zasadę zera, autorzy opisują w jaki sposób możemy uniknąć pisania regułę pięciu operatorów jako przyczyny ich pisanie to:C++ Reguła Zero: polimorficzny usunięć i unique_ptr zachowanie

  1. zarządzanie zasobami
  2. polimorficzne usunięcie

I obie te mogą być załatwione za pomocą inteligentnych wskaźników.

Tutaj szczególnie interesuje mnie druga część.

Rozważmy następujący fragment kodu:

class Base 
{ 
public: 
    virtual void Fun() = 0; 
}; 


class Derived : public Base 
{ 
public: 

    ~Derived() 
    { 
     cout << "Derived::~Derived\n"; 
    } 

    void Fun() 
    { 
     cout << "Derived::Fun\n"; 
    } 
}; 


int main() 
{ 
    shared_ptr<Base> pB = make_shared<Derived>(); 
    pB->Fun(); 
} 

w tym przypadku, jak autorzy artykułu wyjaśnić, otrzymujemy polimorficzny usunięcie za pomocą udostępnionego wskaźnik, i to działa.

Ale jeśli zastąpię shared_ptr z unique_ptr, nie jestem już w stanie obserwować polimorficznej delecji.

Teraz moje pytanie brzmi: dlaczego te dwa zachowania różnią się od siebie? Dlaczego shared_ptr zajmuje się usuwaniem polimorficznym, podczas gdy unique_ptr nie?

+0

jak inicjujesz 'unique_pointer'? –

+0

Czy to ma znaczenie? Tak czy inaczej: unique_ptr pB (new Derived()) – Arun

+6

Ponieważ 'std :: shared_ptr' przenosi wskaźnik do funkcji deletera. Kiedy przypisujesz jedną 'std :: shared_ptr' do kompatybilnej, wskaźnik jest jednym z członków skopiowanych lub przeniesionych. Nie dzieje się tak z 'std :: unique_ptr', a ponieważ twoja klasa bazowa nie ma wirtualnego destruktora, dostajesz tryb bez kości. –

Odpowiedz

3

Masz swoją odpowiedź tutaj: https://stackoverflow.com/a/22861890/2007142

Cytat:

Raz ostatni odnosząc shared_ptr wykracza poza zakres lub zresetowaniu ~Derived() zostanie wywołana, a pamięć zwolniona. W związku z tym nie trzeba wprowadzać wirtualnego ustawienia ~Base(). unique_ptr<Base> i make_unique<Derived> nie udostępniają tej funkcji, ponieważ nie dostarczają one mechaniki shared_ptr w stosunku do deletera, ponieważ unikalny wskaźnik jest znacznie prostszy i ma na celu najniższy narzut, a zatem nie jest przechowywany dodatkowy wskaźnik funkcji potrzebny do deletera .

+0

, ale możesz ustawić inny typ przecinarki niż domyślny i wydaje się, że jest on kopiowany w ... . – Yakk

1
template<typename T> 
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>; 

template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) { 
    return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}}; 
} 

Problem polega na tym, że domyślna Deleter dla unique_ptr wzywa delete na przechowywanego wskaźnika. Powyżej znajduje się delulator, który zna typ na budowie, więc po skopiowaniu do klasy bazowej unique_ptr będzie nadal usuwał jako dziecko.

To dodaje skromne obciążenie, ponieważ musimy wyłuskać wskaźnik. Dodatkowo denormalizuje typ, ponieważ domyślnie skonstruowane są teraz nielegalne. Możesz to naprawić, wykonując dodatkową pracę (zamień surowy wskaźnik funkcyjny na semi smart functor, który przynajmniej się nie zawiesza: wskaźnik funkcji powinien jednak istnieć, jeśli unique nie jest pusty, gdy wywoływacz jest wywoływany) .

+0

Użyłem implementacji z tej odpowiedzi http://stackoverflow.com/a/13512344/602798 i nadal nie daje mi polimorficznego usunięcia – Arun

+0

@Arun Gdzie jest twój wirtualny dtor? –

+0

@Arun, dlaczego nie masz wirtualnego dtora w Bazie? – ooga

2

Będzie działać, jeśli użyjesz C++ 14 make_unique lub napiszesz swój jak w odpowiedzi Yakka. Zasadniczo różnica między wspólną zachowanie wskaźnika jest to, że masz:

template< 
    class T, 
    class Deleter = std::default_delete<T> 
> class unique_ptr; 

dla unique_pointer i jak może widać, Deleter należy do typu. Jeśli zadeklarujesz numer unique_pointer<Base>, zawsze użyjesz domyślnie std::default_delete<Base>. Ale make_unique zajmie się używaniem prawidłowego narzędzia do usuwania klasy.

Podczas korzystania shared_ptr masz:

template< class Y, class Deleter > 
shared_ptr(Y* ptr, Deleter d); 

i inne przeciążenia jak konstruktora. Jak widać domyślny deleter dla unique_ptr zależy od parametru szablonu podczas deklarowania typu (chyba że używasz make_unique), podczas gdy dla shared_ptr, deleter zależy od typu przekazanego do konstruktora.


Można zobaczyć wersję, która pozwala usunąć bez polimorficzny wirtualnego destruktora here (wersja ta powinna także działać w VS2012). Zauważ, że jest dość mocno zhakowana i obecnie nie jestem pewien, jak będzie wyglądać zachowanie unique_ptr i make_shared w C++ 14, ale mam nadzieję, że ułatwią to. Może zajrzę do gazet dla dodatków C++ 14 i zobaczę, czy coś się zmieniło, gdybym miał czas później.

+0

Czy to oznacza, że ​​jeśli utworzyć shared_ptr tak jak shared_ptr pB (new Derived()); Nie otrzymam polimorficznego usunięcia? – Arun

+0

Próbowałem utworzyć shared_ptr, jak wspomniano w moim poprzednim komentarzu. Ale nadal otrzymuję polimorficzne usunięcie (przy użyciu VS2012 mam nadzieję, że nie jest to pewne niestandardowe niestandardowe zachowanie VS) – Arun