Czy powinienem przechowywać całe obiekty, czy też wskaźniki do obiektów w kontenerach?

162

Projektowanie nowego systemu od podstaw. Będę używać STL do przechowywania list i map pewnych długotrwałych obiektów.

Pytanie: Czy powinienem upewnić się, że moje obiekty mają konstruktory kopiujące i przechowują kopie obiektów w moich kontenerach STL, czy też ogólnie lepiej jest samodzielnie zarządzać życiem i zakresem i po prostu przechowywać wskaźniki do tych obiektów w moich kontenerach STL?

Zdaję sobie sprawę, że jest to trochę krótkie w szczegółach, ale szukam „teoretycznej” lepszej odpowiedzi, jeśli ona istnieje, ponieważ wiem, że oba te rozwiązania są możliwe.

Dwie bardzo oczywiste wady zabawy ze wskaźnikami: 1) Muszę samodzielnie zarządzać alokacją / cofnięciem alokacji tych obiektów w zakresie poza STL. 2) Nie mogę utworzyć obiektu tymczasowego na stosie i dodać go do moich kontenerów.

Czy jest coś jeszcze, czego mi brakuje?

Stéphane
źródło
36
Boże, uwielbiam tę stronę, to jest DOKŁADNE pytanie, o którym myślałem dzisiaj ... dziękuję za wykonanie pracy polegającej na zadaniu go za mnie :-)
eviljack
2
kolejną interesującą rzeczą jest to, że powinniśmy sprawdzić, czy wskaźnik został faktycznie dodany do kolekcji, a jeśli tak nie jest, prawdopodobnie powinniśmy wywołać funkcję delete, aby uniknąć wycieków pamięci ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered

Odpowiedzi:

68

Ponieważ ludzie wbijają się w skuteczność używania wskaźników.

Jeśli rozważasz użycie std :: vector i jeśli jest niewiele aktualizacji, a często iterujesz po swojej kolekcji i nie jest to typ polimorficzny, „kopie” przechowujące obiekty będą bardziej efektywne, ponieważ uzyskasz lepszą lokalizację odniesienia.

Otoh, jeśli aktualizacje są powszechne, wskaźniki przechowywania pozwolą zaoszczędzić koszty kopiowania / przenoszenia.

Torbjörn Gyllebring
źródło
7
Jeśli chodzi o lokalizację pamięci podręcznej, przechowywanie wskaźników w wektorze może być wydajne, jeśli jest używane razem z niestandardowym alokatorem dla punktów. Alokator niestandardowy musi zadbać o lokalizację pamięci podręcznej, na przykład za pomocą miejsca docelowego new (patrz en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
amit
47

To naprawdę zależy od twojej sytuacji.

Jeśli twoje obiekty są małe, a wykonanie kopii obiektu jest lekkie, przechowywanie danych w kontenerze STL jest moim zdaniem proste i łatwiejsze w zarządzaniu, ponieważ nie musisz się martwić o zarządzanie okresem życia.

Jeśli obiekty są duże, a posiadanie domyślnego konstruktora nie ma sensu lub kopie obiektów są drogie, to prawdopodobnie najlepszym rozwiązaniem jest przechowywanie za pomocą wskaźników.

Jeśli zdecydujesz się używać wskaźników do obiektów, zapoznaj się z biblioteką kontenerów wskaźników zwiększających . Ta biblioteka przyspieszająca opakowuje wszystkie kontenery STL do użytku z dynamicznie przydzielanymi obiektami.

Każdy kontener wskaźników (na przykład ptr_vector) przejmuje własność obiektu, gdy jest dodawany do kontenera i zarządza okresem istnienia tych obiektów za Ciebie. Masz również dostęp do wszystkich elementów w kontenerze ptr_ przez odniesienie. Dzięki temu możesz robić takie rzeczy jak

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Te klasy zawijają kontenery STL i współpracują ze wszystkimi algorytmami STL, co jest naprawdę przydatne.

Istnieją również narzędzia do przenoszenia własności wskaźnika w kontenerze na wywołującego (za pomocą funkcji zwalniania w większości kontenerów).

Nick Haddad
źródło
38

Jeśli przechowujesz obiekty polimorficzne, zawsze musisz używać kolekcji wskaźników klasy bazowej.

Oznacza to, że jeśli planujesz przechowywać w swojej kolekcji różne typy pochodne, musisz przechowywać wskaźniki lub zostać zjedzonym przez krojącego demona.

Torbjörn Gyllebring
źródło
1
Uwielbiałem krojonego demona!
idichekop
22

Przepraszam, że skaczę za 3 lata po wydarzeniu, ale uwaga tutaj ...

W moim ostatnim dużym projekcie centralna struktura danych była zbiorem dość prostych obiektów. Około roku po rozpoczęciu projektu, wraz z ewolucją wymagań, zdałem sobie sprawę, że obiekt faktycznie musi być polimorficzny. Potrzeba było kilku tygodni trudnej i nieprzyjemnej operacji mózgu, aby naprawić strukturę danych tak, aby była zbiorem wskaźników klasy bazowej i poradzić sobie ze wszystkimi uszkodzeniami ubocznymi podczas przechowywania obiektów, rzutowania i tak dalej. Kilka miesięcy zajęło mi przekonanie się, że nowy kod działa. Nawiasem mówiąc, to skłoniło mnie do zastanowienia się, jak dobrze zaprojektowany jest model obiektowy C ++.

W moim obecnym dużym projekcie moja centralna struktura danych to zestaw dość prostych obiektów. Około roku po rozpoczęciu projektu (co dzieje się dzisiaj) zdałem sobie sprawę, że obiekt musi być faktycznie polimorficzny. Wróciłem do sieci, znalazłem ten wątek i odnalazłem łącze Nicka do biblioteki kontenerów wskaźników Boost. To jest dokładnie to, co musiałem napisać ostatnim razem, aby wszystko naprawić, więc tym razem spróbuję.

W każdym razie morał dla mnie: jeśli twoja specyfikacja nie jest w 100% odlana z kamienia, idź po wskazówki, a możesz potencjalnie zaoszczędzić sobie dużo pracy później.

EML
źródło
Specyfikacje nigdy nie są osadzone w kamieniu. Nie sądzę, żeby to oznaczało, że powinieneś używać wyłącznie kontenerów wskaźników, chociaż kontenery wskaźnika doładowania wydają się znacznie uatrakcyjniać tę opcję. Jestem sceptyczny, że jeśli zdecydujesz, że kontener obiektów powinien zostać przekonwertowany na kontener wskaźnika, musisz od razu zmienić cały program. Może tak być w przypadku niektórych projektów. W takim razie jest to delikatny projekt. W takim przypadku nie obwiniaj swojego problemu „słabością” kontenerów obiektów.
allyourcode
Mogłeś zostawić element w wektorze z semantyką wartości i wewnątrz zachować polimorficzne zachowanie.
Billy ONeal
19

Dlaczego nie wykorzystać tego, co najlepsze z obu światów: zrób kontener inteligentnych wskaźników (takich jak boost::shared_ptrlub std::shared_ptr). Nie musisz zarządzać pamięcią i nie musisz zajmować się dużymi operacjami kopiowania.

Branan
źródło
Czym to podejście różni się od tego, co zasugerował Nick Haddad, korzystając z biblioteki Boost Pointer Container Library?
Thorsten Schöning
10
@ ThorstenSchöning std :: shared_ptr nie dodaje zależności od boost.
James Johnston
Nie możesz używać wspólnych wskaźników do obsługi swojego polimorfizmu, więc w końcu przegapisz tę funkcję przy takim podejściu, chyba że wyraźnie rzucisz wskaźniki
auserdude
11

Generalnie przechowywanie obiektów bezpośrednio w kontenerze STL jest najlepsze, ponieważ jest najprostsze, najbardziej wydajne i najłatwiejsze w użyciu obiektu.

Jeśli sam obiekt ma składnię niepodlegającą kopiowaniu lub jest abstrakcyjnym typem podstawowym, będziesz musiał przechowywać wskaźniki (najłatwiej jest użyć shared_ptr)

Greg Rogers
źródło
4
Nie jest to najbardziej wydajne, jeśli obiekty są duże i często przenosisz elementy.
allyourcode
3

Wydaje się, że dobrze rozumiesz różnicę. Jeśli obiekty są małe i łatwe do skopiowania, to jak najbardziej je przechowuj.

Jeśli nie, pomyślałbym o przechowywaniu inteligentnych wskaźników (nie auto_ptr, inteligentnego wskaźnika liczącego ref) do tych, które przydzielasz na stercie. Oczywiście, jeśli zdecydujesz się na inteligentne wskaźniki, nie możesz przechowywać obiektów przydzielonych do tymczasowego stosu (jak powiedziałeś).

@ Torbjörn mówi o krojeniu.

Lou Franco
źródło
1
Och, i nigdy przenigdy nie twórz kolekcji auto_ptr
Torbjörn Gyllebring
Racja, auto_ptr nie jest inteligentnym wskaźnikiem - nie liczy ref.
Lou Franco,
auto_ptr nie ma też nieniszczącej semantyki kopiowania. Czynność przypisania auto_ptr od onedo anotherspowoduje zwolnienie odniesienia z onei zmianę one.
Andy Finkenstadt
2

Jeśli obiekty mają odnosić się do innego miejsca w kodzie, zapisz w wektorze boost :: shared_ptr. Zapewnia to, że wskaźniki do obiektu pozostaną prawidłowe, jeśli zmienisz rozmiar wektora.

To znaczy:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Jeśli nikt inny nie przechowuje wskaźników do obiektów lub lista nie rośnie i nie zmniejsza się, po prostu przechowuj jako zwykłe stare obiekty:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

źródło
1

To pytanie od jakiegoś czasu mnie dręczy.

Skłaniam się do przechowywania wskaźników, ale mam pewne dodatkowe wymagania (opakowania SWIG lua), które mogą Cię nie dotyczyć.

Najważniejszym punktem w tym poście jest przetestowanie przy użyciu swoich obiektów

Zrobiłem to dzisiaj, aby przetestować szybkość wywoływania funkcji składowej na kolekcji 10 milionów obiektów, 500 razy.

Funkcja aktualizuje x i y na podstawie xdir i ydir (wszystkie zmienne typu float).

Użyłem std :: list do przechowywania obu typów obiektów i stwierdziłem, że przechowywanie obiektu na liście jest nieco szybsze niż użycie wskaźnika. Z drugiej strony wydajność była bardzo zbliżona, więc wszystko sprowadza się do tego, jak zostaną wykorzystane w Twojej aplikacji.

Dla porównania, przy -O3 na moim sprzęcie, wskaźniki zajęły 41 sekund, a surowe obiekty - 30 sekund.

Meleneth
źródło