2015-05-18 10 views
7

Co jest takiego złego w robieniu czegoś takiego?rozszerzenie shared_ptr o dziedziczenie

class myclass : public std::shared_ptr<myotherclass> { 
    // some code, an allocation is never done 
    std::string get_info() { 
      if(*this != nullptr) return "<info>" + (this->info * 3) + "</info>"; 
      else return ""; 
    } 
}; 

gdy nie dokonano przydziału w klasie --- chodzi tylko o zapewnienie dekoracji jak wyżej?

+2

Co jest takiego dobrego w robieniu czegoś takiego? – Drax

+0

W większości przypadków powinieneś być w stanie opisać związek dziedziczenia z fazą angielską "is a". Więc w powyższym przypadku mówisz: myclass "jest" std :: shared_ptr . Prawdopodobnie nie o to ci chodzi. –

+0

To nie jest tak źle jak "dziwne". Z pewnością powinien to być członek 'myotherclass' sam (lub może nie będący członkiem działający na' myotherclass'), a nie coś przykręconego do jakiegoś konkretnego typu inteligentnego wskaźnika? –

Odpowiedz

7

W zasadzie można czerpać z klas STL, patrz: here i here. Musisz jednak pamiętać, że nie powinieneś pracować z wskaźnikiem do klasy bazowej - w tym przypadku jest to std::shared_ptr<myotherclass>*.

Więc i jego warianty powinny być zakazane:

std::shared_ptr<myotherclass>* ptr = new myclass(/* ... */); 

... ale zgodzili się, że wygląda syntetycznych bitów.

Dlaczego jest to zabronione? Ponieważ klasy STL nie mają wirtualnego destruktora. Więc jeśli chcesz uzyskać przydzieloną klasę, część pochodna pozostaje. To z kolei wywołuje undefined behaviour i prawdopodobnie tworzy przeciek pamięci - nawet jeśli nie masz pewnych alokacji w swojej klasie pochodnej.

W tym celu, jedną z możliwości jest czerpać prywatnie od shared_ptr:

class myclass : private std::shared_ptr<myotherclass> {}; 
       ^^^^^^^ 

To jednak może doprowadzić do problemów z kompatybilności binarnej, patrz komentarze do this answer.

Na ręce, nawet jeśli pierwsza jest dozwolona, ​​można przejść na mniej podatne na błędy i albo użyć kompozycji, gdzie dokonać shared_ptr członkiem myclass i wystawiać wymaganą funkcjonalność (z wadą, że czasami masz bardzo dużo eksponować). Lub możesz ustawić autonomiczną funkcję, która robi to, co chcesz ... Wiem, że to wiedziałeś ;-)

+1

'std :: shared_ptr * ptr = ...' jest nonsensem. Jeśli wirtualny destruktor staje się konieczny dla klasy RAII, coś już zostało źle wykorzystane. – Potatoswatter

+0

@davidhigh - bardzo kompletna odpowiedź: pokazuje ograniczenia użyteczności ("nie powinieneś pracować ze wskaźnikiem do klasy bazowej"), wskazuje na odniesienia (w jednym z nich mogłem dokładnie zobaczyć, gdzie na specyfikacji popełniłem błąd) i demonstruje zrozumienie tego, co próbował osiągnąć kwestionariusz ("ujawnia wymaganą funkcjonalność (z tą wadą, że czasami trzeba dużo eksponować) lub możesz ustawić funkcję samodzielną"). – ribamar

+0

@Potatoswatter: 'std :: shared_ptr * ptr = ...' * jest * nonsensem, oczywiście ... i tak absurdalnie, tak miejmy nadzieję, że nikt nigdy nie zrobi tego przez pomyłkę. – davidhigh

1

Ponieważ nigdy nie będziesz ręcznie delete go (i nigdy nie powinieneś ręcznie delete niczego, co jest raczej punktem shared_ptr), wirtualne destruktory nie stanowią problemu.

Niektóre problemy z interoperacyjnością mogą jednak pojawić się.

  1. Otrzymujesz swoją klasę pochodną tylko podczas tworzenia jej określonych instancji. Gdy otrzymasz numer shared_ptr z innego miejsca niż get_shared_from_this, nie będzie on zawierał Twojego info.

  2. Szablony funkcji przeciążone na shared_ptr<T> nie zobaczą dziedziczenia. Twoja klasa pochodna nagle pojawi się obca dla funkcji losowych, takich jak std::static_pointer_cast.

Na szczęście biblioteka standardowa C++ jest wypełniona zgrabnymi haczykami.Można zainstalować niestandardowy Deleter tak:

template< typename t > 
struct my_deleter 
    : std::default_delete<t> { 
    std::string info; 

    my_deleter(std::string in_info) 
     : info(std::move(in_info)) {} 
}; 

std::shared_pointer<foo> myfoo(new foo, my_deleter{ "it's a foo" }); 

i pobierać informacje z funkcją trzeciego:

template< typename t > 
std::string get_my_info(std::shared_ptr<t> ptr) { 
    my_deleter<t> * dp = std::get_deleter< my_deleter<t> >(ptr); 
    if (! dp) return {}; 
    return dp->info; 
} 

To nie jest bardzo dobrej architektury programu, ponieważ jest tylko jeden zwyczaj Deleter slot na dzielony obiekt. Może jednak zrobić szczyptę.