Ok, to jest naprawdę trudne do przyznania się, ale w tej chwili mam silną pokusę, aby odziedziczyć std::vector
.
Potrzebuję około 10 niestandardowych algorytmów dla wektora i chcę, aby były one bezpośrednio elementami wektora. Ale oczywiście chcę też mieć resztę std::vector
interfejsu. Cóż, moim pierwszym pomysłem, jako obywatela przestrzegającego prawa, było mieć std::vector
członka w MyVector
klasie. Ale wtedy musiałbym ręcznie zresetować cały interfejs std :: vector. Za dużo do napisania. Następnie pomyślałem o dziedziczeniu prywatnym, aby zamiast metod usprawiedliwiania napisać kilka z nich using std::vector::member
w części publicznej. To też jest nużące.
I oto jestem, naprawdę uważam, że mogę po prostu odziedziczyć po społeczeństwie std::vector
, ale ostrzegam w dokumentacji, że tej klasy nie należy używać polimorficznie. Myślę, że większość programistów jest wystarczająco kompetentna, aby zrozumieć, że i tak nie należy z tego korzystać polimorficznie.
Czy moja decyzja jest całkowicie nieuzasadniona? Jeśli tak, to dlaczego? Czy możesz podać alternatywę, w której dodatkowi członkowie faktycznie byliby członkami, ale nie wymagałby ponownego wpisywania całego interfejsu wektora? Wątpię, ale jeśli możesz, po prostu będę szczęśliwy.
Poza tym, że jakiś idiota potrafi napisać coś podobnego
std::vector<int>* p = new MyVector
czy istnieje inne realistyczne zagrożenie w korzystaniu z MyVector? Mówiąc realistycznie, odrzucam takie rzeczy, jak wyobrażenie sobie funkcji, która przenosi wskaźnik do wektora ...
Cóż, przedstawiłem swoją sprawę. Zgrzeszyłem. Teraz to Ty musisz mi wybaczyć, czy nie :)
std::vector
jest dość ogromny, a kiedy pojawi się C ++ 1x, znacznie się rozszerzy. To dużo do napisania i więcej do rozszerzenia w ciągu kilku lat. Myślę, że jest to dobry powód, aby rozważyć dziedziczenie zamiast ograniczania - jeśli przyjmie się założenie, że te funkcje powinny być członkami (w co wątpię). Zasadą, aby nie czerpać z kontenerów STL jest to, że nie są one polimorficzne. Jeśli nie używasz ich w ten sposób, nie ma to zastosowania.Odpowiedzi:
W rzeczywistości nie ma nic złego w publicznym dziedziczeniu
std::vector
. Jeśli potrzebujesz tego, po prostu zrób to.Sugerowałbym zrobienie tego tylko wtedy, gdy jest to naprawdę konieczne. Tylko wtedy, gdy nie możesz robić tego, co chcesz z darmowymi funkcjami (np. Powinien zachować pewien stan).
Problemem jest
MyVector
jest to nowy byt. Oznacza to, że nowy programista C ++ powinien wiedzieć, co to do cholery jest, zanim go użyje. Jaka jest różnica międzystd::vector
iMyVector
? Którego lepiej użyć tu i tam? Co zrobić, aby przenieśćstd::vector
sięMyVector
? Czy mogę po prostu użyć,swap()
czy nie?Nie twórz nowych bytów tylko po to, aby coś wyglądało lepiej. Te istoty (szczególnie takie powszechne) nie będą żyć w próżni. Będą żyć w mieszanym środowisku ze stale rosnącą entropią.
źródło
MyVector
a następnie nie próbuj przekazać ich do funkcji, które akceptująstd::vector&
lubstd::vector*
. Jeśli istnieje jakiekolwiek przypisanie kopii przy użyciu std :: vector * lub std :: vector &, mamy problemy z wycinaniem, w których nowe elementy danychMyVector
nie zostaną skopiowane. To samo dotyczy wywoływania swap poprzez podstawowy wskaźnik / referencję. Myślę, że jakakolwiek hierarchia dziedziczenia, która ryzykuje wycinanie obiektów, jest zła.std::vector
Destruktor nie jestvirtual
, dlatego nigdy nie należy po nim dziedziczyćCały STL został zaprojektowany w taki sposób, że algorytmy i kontenery są oddzielne .
Doprowadziło to do powstania koncepcji różnych typów iteratorów: iteratorów const, iteratorów losowego dostępu itp.
Dlatego zalecam przyjęcie tej konwencji i zaprojektowanie algorytmów w taki sposób, aby nie dbały o to, nad jakim kontenerem pracują - i wymagałyby tylko określonego typu iteratora, który musiałby wykonać operacje.
Pozwól też, że przekieruję cię do kilku dobrych uwag Jeffa Attwooda .
źródło
Głównym powodem, dla którego nie odziedziczysz
std::vector
publicznie, jest brak wirtualnego destruktora, który skutecznie zapobiega polimorficznemu użyciu potomków. W szczególności, to są niedozwolone dodelete
Astd::vector<T>*
które faktycznie punktów uzyskanych w obiekcie (nawet jeśli klasa pochodna dodaje żadnych członków), ale generalnie nie kompilator może ostrzec cię o tym.W tych warunkach dozwolone jest prywatne dziedziczenie. Dlatego zalecam korzystanie z dziedziczenia prywatnego i przekazywanie wymaganych metod od rodzica, jak pokazano poniżej.
Najpierw zastanów się nad refaktoryzacją algorytmów, aby wyodrębnić typ kontenera, na którym operują, i pozostaw je jako bezpłatne funkcje szablonowe, jak zauważyła większość ankieterów. Zwykle odbywa się to poprzez zaakceptowanie przez algorytm pary iteratorów zamiast kontenera jako argumentów.
źródło
vector
przydzielona pamięć nie jest problemem - w końcuvector
destruktor byłby wywoływany przez wskaźnik dovector
. Tyle, że standard zabraniadelete
tworzenia darmowych obiektów magazynowych poprzez wyrażenie klasy podstawowej. Powodem jest z pewnością to, że mechanizm (de) alokacji może próbować wywnioskować wielkość porcji pamięci, aby zwolnić ją zdelete
argumentu operacji, na przykład, gdy istnieje kilka aren alokacji dla obiektów o określonych rozmiarach. To ograniczenie nie ma zastosowania do normalnego niszczenia przedmiotów o statycznym lub automatycznym czasie przechowywania.Jeśli zastanawiasz się nad tym, najwyraźniej już zabiłeś pedantów językowych w swoim biurze. Z nimi na uboczu, dlaczego po prostu nie zrobić
Pozwoli to ominąć wszystkie możliwe błędy, które mogą wyniknąć z przypadkowego upcastingu twojej klasy MyVector, i nadal możesz uzyskać dostęp do wszystkich operacji wektorowych, dodając trochę
.v
.źródło
Co masz nadzieję osiągnąć? Udostępniasz tylko niektóre funkcje?
Idiomatycznym sposobem na to w C ++ jest napisanie kilku darmowych funkcji, które implementują tę funkcjonalność. Możliwe, że tak naprawdę nie potrzebujesz std :: vector, szczególnie dla implementowanej funkcjonalności, co oznacza, że faktycznie tracisz możliwość ponownego użycia, próbując odziedziczyć po std :: vector.
Radziłbym, abyś spojrzał na standardową bibliotekę i nagłówki i zastanowił się, jak działają.
źródło
front
iback
funkcje zbyt. :) (begin
end
Myślę, że bardzo mało zasad należy przestrzegać na ślepo w 100% przypadków. Wygląda na to, że zastanowiłeś się nad tym i jesteś przekonany, że to jest właściwa droga. Tak więc - chyba że ktoś wymyśli dobre konkretne powody, aby tego nie robić - uważam, że powinieneś zacząć realizować swój plan.
źródło
Nie ma powodu do dziedziczenia,
std::vector
chyba że ktoś chce stworzyć klasę, która działa inaczej niżstd::vector
, ponieważ obsługuje na swój sposób ukryte szczegółystd::vector
definicji lub chyba, że ma się ideologiczne powody, aby używać obiektów takiej klasy zamiaststd::vector
te. Jednak twórcy standardu w C ++ nie dostarczylistd::vector
żadnego interfejsu (w postaci chronionych elementów), z którego mogłaby skorzystać taka odziedziczona klasa w celu ulepszenia wektora w określony sposób. Rzeczywiście, nie mieli sposobu na wymyślenie żadnego konkretnego aspektu, który mógłby wymagać rozszerzenia lub dostrojenia dodatkowej implementacji, więc nie musieli myśleć o zapewnieniu takiego interfejsu w jakimkolwiek celu.Przyczyny drugiej opcji mogą być jedynie ideologiczne, ponieważ
std::vector
nie są polimorficzne, a poza tym nie ma różnicy, czy ujawniszstd::vector
publiczny interfejs poprzez publiczne dziedzictwo czy członkostwo publiczne. (Załóżmy, że musisz zachować pewien stan w swoim obiekcie, aby nie można było uciec od bezpłatnych funkcji). Z mniej solidnej nuty iz ideologicznego punktu widzenia wydaje się, żestd::vector
są one rodzajem „prostej idei”, więc jakakolwiek złożoność w postaci obiektów różnych możliwych klas w ich miejscu ideologicznie nie ma sensu.źródło
W praktyce: jeśli nie masz żadnych członków danych w klasie pochodnej, nie masz żadnych problemów, nawet w przypadku użycia polimorficznego. Potrzebujesz wirtualnego destruktora tylko wtedy, gdy rozmiary klasy bazowej i pochodnej są różne i / lub masz funkcje wirtualne (co oznacza tabelę v).
ALE w teorii: z [wyra. Usuń] w C ++ 0x FCD: W pierwszej alternatywie (usuń obiekt), jeśli typ statyczny obiektu do usunięcia jest inny niż jego typ dynamiczny, typ statyczny powinien być klasa bazowa typu dynamicznego obiektu, który ma zostać usunięty, a typ statyczny ma wirtualny destruktor lub zachowanie jest niezdefiniowane.
Ale możesz bez problemu czerpać prywatnie ze std :: vector. Użyłem następującego wzoru:
źródło
[expr.delete]
w C ++ 0x FCD: <quote> W pierwszej alternatywie (usuń obiekt), jeśli typ statyczny obiektu do usunięcia różni się od jego typu dynamicznego, typ statyczny powinien być klasą bazową typu dynamicznego obiektu, który ma zostać usunięty, a typ statyczny powinien mieć wirtualny niszczyciel lub zachowanie jest niezdefiniowane. </quote>Jeśli przestrzegasz dobrego stylu C ++, brak funkcji wirtualnej nie jest problemem, ale krojeniem (patrz https://stackoverflow.com/a/14461532/877329 )
Dlaczego brak funkcji wirtualnych nie jest problemem? Ponieważ funkcja nie powinna próbować
delete
odbierać żadnego wskaźnika, ponieważ nie jest jej właścicielem. Dlatego też, jeśli przestrzegane są surowe zasady własności, wirtualne niszczyciele nie powinny być potrzebne. Na przykład zawsze jest to źle (z wirtualnym destruktorem lub bez niego):W przeciwieństwie do tego zawsze będzie działać (z wirtualnym destruktorem lub bez niego):
Jeśli obiekt jest tworzony przez fabrykę, fabryka powinna również zwrócić wskaźnik do działającego urządzenia do usuwania, którego należy użyć zamiast
delete
, ponieważ fabryka może używać własnej sterty. Dzwoniący może uzyskać formęshare_ptr
lubunique_ptr
. W skrócie, NIEdelete
nic nie dostać bezpośrednio odnew
.źródło
Tak, jest bezpieczny, o ile uważasz, aby nie robić rzeczy, które nie są bezpieczne ... Nie sądzę, żebym kiedykolwiek widział, żeby ktoś używał wektora z nowym, więc w praktyce prawdopodobnie wszystko będzie dobrze. Jednak nie jest to powszechny idiom w c ++ ....
Czy możesz podać więcej informacji na temat algorytmów?
Czasami kończy się to, że podążasz jedną drogą z projektem, a potem nie widzisz innych ścieżek, które mogłeś obrać - fakt, że twierdzisz, że potrzebujesz wektora za pomocą 10 nowych algorytmów, dzwoni mi dzwonek alarmowy - czy naprawdę jest 10 ogólnych celów algorytmy, które wektor może zaimplementować, czy próbujesz stworzyć obiekt, który jest jednocześnie wektorem ogólnego przeznaczenia ORAZ zawierającym funkcje specyficzne dla aplikacji?
Na pewno nie mówię, że nie powinieneś tego robić, po prostu z informacją, którą podałeś, dzwonią budziki, co sprawia, że myślę, że może coś jest nie tak z twoimi abstrakcjami i istnieje lepszy sposób na osiągnięcie tego, co masz chcieć.
źródło
Ja też odziedziczyłem po
std::vector
niedawno i , że jest bardzo przydatny i do tej pory nie miałem z tym żadnych problemów.Moja klasa jest rzadką klasą macierzy, co oznacza, że muszę gdzieś przechowywać elementy macierzy, a mianowicie w
std::vector
. Powodem dziedziczenia było to, że byłem trochę zbyt leniwy, aby pisać interfejsy do wszystkich metod, a także łączę klasę z Pythonem poprzez SWIG, gdzie jest już dobry kod interfejsu dlastd::vector
. Znacznie łatwiej mi było rozszerzyć ten kod interfejsu na moją klasę niż pisać nowy od zera.Jedyny problem widzę z podejściem jest nie tyle ze bez wirtualnego destruktora, ale niektóre inne metody, które chciałbym przeciążenia, takie jak
push_back()
,resize()
,insert()
itp Prywatne dziedziczenie rzeczywiście może być dobrym rozwiązaniem.Dzięki!
źródło
Tutaj pozwól, że przedstawię jeszcze 2 sposoby na robienie tego, czego chcesz. Jeden jest innym sposobem na zawinięcie
std::vector
, innym jest sposób na dziedziczenie bez dawania użytkownikom możliwości zniszczenia czegokolwiek:std::vector
bez pisania wielu opakowań funkcji.std::vector
i unikanie problemu dtor.źródło
To pytanie z pewnością wywoła bezdechowe sprzęganie pereł, ale w rzeczywistości nie ma uzasadnionego powodu do unikania lub „niepotrzebnego mnożenia bytów”, aby uniknąć wyprowadzania ze standardowego pojemnika. Najprostsze, najkrótsze możliwe wyrażenie jest najczystsze i najlepsze.
Musisz zachować wszelką zwykłą ostrożność wokół dowolnego typu pochodnego, ale nie ma nic szczególnego w przypadku podstawy ze Standardu. Przesłonięcie funkcji elementu podstawowego może być trudne, ale byłoby to nierozsądne w przypadku dowolnej niewirtualnej bazy, więc nie ma tu nic specjalnego. Jeśli miałbyś dodać członka danych, musiałbyś się martwić o krojenie, gdyby członek musiał być spójny z zawartością bazy, ale znowu to samo dla każdej bazy.
Odkryłem, że szczególnie użyteczne jest wykorzystanie standardowego kontenera do dodania jednego konstruktora, który wykonuje dokładnie potrzebną inicjalizację, bez szansy na pomyłkę lub przejęcie przez innych konstruktorów. (Patrzę na ciebie, konstruktory lista_inicjalizacji!) Następnie możesz swobodnie korzystać z wynikowego obiektu pociętego na plasterki - przekazać go przez odniesienie do czegoś oczekującego podstawy, przejść od tego do instancji podstawy, co masz. Nie ma powodów do zmartwień, chyba że wiązałoby się to z powiązaniem argumentu szablonu z klasą pochodną.
Miejscem, w którym ta technika będzie natychmiast przydatna w C ++ 20 jest rezerwacja. Gdzie mogliśmy napisać
możemy powiedzieć
a następnie, nawet jako członkowie klasy,
(zgodnie z preferencjami) i nie trzeba pisać konstruktora, aby wywołać dla nich rezerwę ().
(
reserve_in
Technicznie powodem , dla którego trzeba czekać na C ++ 20, jest to, że wcześniejsze standardy nie wymagają zachowania pojemności pustego wektora między ruchami. Jest to uznane za niedopatrzenie i można zasadnie oczekiwać, że zostanie naprawione jako wada czasu na rok 20. Możemy również oczekiwać, że poprawka zostanie skutecznie zaktualizowana w stosunku do poprzednich standardów, ponieważ wszystkie istniejące implementacje faktycznie zachowują zdolność do przenoszenia podczas ruchów; normy po prostu tego nie wymagały. Chętni mogą bezpiecznie skakać rezerwowanie broni prawie zawsze jest tylko optymalizacją).Niektórzy twierdzą, że przypadek
reserve_in
lepiej obsługuje darmowy szablon funkcji:Taka alternatywa jest z pewnością realna - a czasami może być nieskończenie szybsza z powodu * RVO. Ale wybór funkcji pochodnej lub funkcji swobodnej powinien być dokonywany na podstawie własnych zalet, a nie bezpodstawnego (heh!) Przesądu o pochodzeniu ze składników standardowych. W powyższym przykładzie tylko druga forma będzie działać z funkcją swobodną; chociaż poza kontekstem klasowym można to napisać nieco bardziej zwięźle:
źródło