Nie będziesz dziedziczyć po std :: vector


189

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::vectorinterfejsu. Cóż, moim pierwszym pomysłem, jako obywatela przestrzegającego prawa, było mieć std::vectorczłonka w MyVectorklasie. 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::memberw 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 :)


9
Więc w zasadzie pytasz, czy możesz naruszyć wspólną zasadę, ponieważ jesteś zbyt leniwy, aby ponownie wdrożyć interfejs kontenera? Więc nie, nie jest. Widzisz, możesz mieć to, co najlepsze z obu światów, jeśli połkniesz gorzką pigułkę i zrobisz to właściwie. Nie bądź tym facetem. Napisz solidny kod.
Jim Brissom,

7
Dlaczego nie możesz / nie chcesz dodać potrzebnej funkcjonalności do funkcji nie będących członkami? Dla mnie byłoby to najbezpieczniejsze w tym scenariuszu.
Simone

11
Interfejs @Jim: std::vectorjest 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.
sbi

9
Prawdziwe mięso tego pytania znajduje się w jednym zdaniu: „Chcę, aby były bezpośrednio członkami wektora”. Nic innego w pytaniu tak naprawdę nie ma znaczenia. Dlaczego tego „chcesz”? Na czym polega problem z udostępnianiem tej funkcji jako osób niebędących członkami?
czerwiec

8
@JoshC: „Powinieneś” zawsze był bardziej powszechny niż „powinieneś”, i jest to również wersja znaleziona w Biblii Króla Jakuba (do czego ludzie zwykle nawiązują pisząc „nie powinieneś [...] „). Co na ziemi skłoniłoby cię do nazwania go „błędną pisownią”?
ruakh

Odpowiedzi:


155

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ędzy std::vectori MyVector? Którego lepiej użyć tu i tam? Co zrobić, aby przenieść std::vectorsię 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ą.


7
Moim jedynym przeciwwskazaniem do tego jest to, że naprawdę trzeba wiedzieć, co on robi, aby to zrobić. Na przykład nie wprowadzaj dodatkowych elementów danych, MyVectora następnie nie próbuj przekazać ich do funkcji, które akceptują std::vector&lub std::vector*. Jeśli istnieje jakiekolwiek przypisanie kopii przy użyciu std :: vector * lub std :: vector &, mamy problemy z wycinaniem, w których nowe elementy danych MyVectornie 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.
stinky472

13
std::vectorDestruktor nie jest virtual, dlatego nigdy nie należy po nim dziedziczyć
André Fratelli

2
Stworzyłem klasę, która publicznie odziedziczyła std :: vector z tego powodu: miałem stary kod z klasą wektorową inną niż STL i chciałem przejść do STL. Zaimplementowałem starą klasę jako klasę pochodną std :: vector, pozwalając mi nadal używać starych nazw funkcji (np. Count () zamiast size ()) w starym kodzie, podczas pisania nowego kodu przy użyciu std :: vector Funkcje. Nie dodałem żadnych elementów danych, dlatego destruktor std :: vector działał dobrze dla obiektów utworzonych na stercie.
Graham Asher

3
@GrahamAsher Jeśli usuniesz obiekt za pomocą wskaźnika do bazy, a destruktor nie jest wirtualny, twój program zachowuje się w niezdefiniowany sposób. Jednym z możliwych skutków nieokreślonego zachowania jest „działało dobrze w moich testach”. Innym jest to, że wysyła e-maili do babci historię przeglądania sieci. Oba są zgodne ze standardem C ++. Zgodne jest również zmienianie się z jednego na drugi z punktowymi wydaniami kompilatorów, systemów operacyjnych lub fazy księżyca.
Jak - Adam Nevraumont,

2
@GrahamAsher Nie, za każdym razem, gdy usuwasz dowolny obiekt za pomocą wskaźnika do bazy bez wirtualnego destruktora, jest to niezdefiniowane zachowanie zgodnie ze standardem. Rozumiem, co myślisz, co się dzieje; po prostu się mylisz. „wywoływany jest destruktor klasy podstawowej i działa” to jeden z możliwych symptomów (i najczęstszy) tego niezdefiniowanego zachowania, ponieważ jest to naiwny kod maszynowy, który zwykle generuje kompilator. To nie czyni go bezpiecznym ani świetnym pomysłem.
Yakk - Adam Nevraumont,

92

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 .


63

Głównym powodem, dla którego nie odziedziczysz std::vectorpublicznie, jest brak wirtualnego destruktora, który skutecznie zapobiega polimorficznemu użyciu potomków. W szczególności, to są niedozwolone do deleteAstd::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.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

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.


IIUC, brak wirtualnego destruktora stanowi problem tylko wtedy, gdy klasa pochodna przydziela zasoby, które należy uwolnić po zniszczeniu. (Nie byłyby one uwolnione w przypadku użycia polimorficznego, ponieważ kontekst nieświadomie przejmujący własność obiektu pochodnego przez wskaźnik do bazy wywoływałby bazowy destruktor tylko wtedy, gdy jest to czas.) Podobne problemy powstają w przypadku innych zastąpionych funkcji składowych, dlatego należy zachować ostrożność należy wziąć pod uwagę, że podstawowe mogą zadzwonić. Ale przy braku dodatkowych zasobów, czy są jakieś inne powody?
Peter - Przywróć Monikę

2
vectorprzydzielona pamięć nie jest problemem - w końcu vectordestruktor byłby wywoływany przez wskaźnik do vector. Tyle, że standard zabrania deletetworzenia 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ą z deleteargumentu 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.
Peter - Przywróć Monikę

@DavisHerring Myślę, że się tam zgadzamy :-).
Peter - Przywróć Monikę

@ DavisHerring Ach, rozumiem, odwołujesz się do mojego pierwszego komentarza - w tym komentarzu był IIUC i zakończyło się ono pytaniem; Później zobaczyłem, że rzeczywiście zawsze jest to zabronione. (Basilevs wypowiedział się ogólnie: „skutecznie zapobiega”, a ja zastanawiałem się nad konkretnym sposobem, w jaki temu zapobiega.) Tak, zgadzamy się: UB.
Peter - Przywróć Monikę

@Basilevs To musiało być niezamierzone. Naprawiony.
ThomasMcLeod

36

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ć

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

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.


A odsłaniając kontenery i algorytmy? Zobacz odpowiedź Kos powyżej.
bruno nery


19

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ą.


5
Nie jestem przekonana. Czy możesz zaktualizować niektóre z proponowanego kodu, aby wyjaśnić dlaczego?
Karl Knechtel,

6
@Armen: czy oprócz estetyki istnieją jakieś dobre powody?
snemarch

12
@Armen: lepsza estetyka i większa genericity, byłoby zapewnienie darmo fronti backfunkcje zbyt. :) ( beginend
Weź

3
Nadal nie rozumiem, co jest nie tak z darmowymi funkcjami. Jeśli nie podoba ci się „estetyka” STL, być może C ++ jest dla ciebie złym miejscem, pod względem estetycznym. Dodanie niektórych funkcji składowych nie naprawi tego, ponieważ wiele innych algorytmów jest nadal funkcjami bezpłatnymi.
Frank Osterfeld,

17
Trudno jest buforować wynik ciężkiej operacji w zewnętrznym algorytmie. Załóżmy, że musisz obliczyć sumę wszystkich elementów w wektorze lub rozwiązać równanie wielomianowe z elementami wektorowymi jako współczynnikami. Te operacje są ciężkie i przydałoby się im lenistwo. Ale nie można go wprowadzić bez opakowania lub dziedziczenia z pojemnika.
Basilevs,

14

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.


9
Twoje pierwsze zdanie jest prawdziwe w 100% przypadków. :)
Steve Fallows,

5
Niestety drugie zdanie nie jest. Nie zastanawiał się długo. Większość pytań jest nieistotna. Jedyną jego częścią, która pokazuje jego motywację, jest „Chcę, aby byli oni bezpośrednio członkami wektora”. Chcę. Nie ma powodu, dla którego jest to pożądane. Brzmi to tak, jakby wcale nie myślał .
czerwiec

7

Nie ma powodu do dziedziczenia, std::vectorchyba że ktoś chce stworzyć klasę, która działa inaczej niż std::vector, ponieważ obsługuje na swój sposób ukryte szczegóły std::vectordefinicji lub chyba, że ​​ma się ideologiczne powody, aby używać obiektów takiej klasy zamiast std::vectorte. Jednak twórcy standardu w C ++ nie dostarczyli std::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::vectornie są polimorficzne, a poza tym nie ma różnicy, czy ujawnisz std::vectorpubliczny 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ę, że std::vectorsą 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.


Świetna odpowiedź. Witamy w SO!
Armen Tsirunyan

4

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:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...

3
„Potrzebujesz wirtualnego destruktora tylko wtedy, gdy rozmiary klasy bazowej i pochodnej są różne nad / lub masz funkcje wirtualne (co oznacza tabelę v)”. Twierdzenie to jest praktycznie poprawne, ale nie teoretycznie
Armen Tsirunyan

2
tak, w zasadzie jest to nadal niezdefiniowane zachowanie.
czerwiec

Jeśli twierdzisz, że jest to zachowanie nieokreślone, chciałbym zobaczyć dowód (cytat ze standardu).
hmuelner

8
@hmuelner: Niestety, Armen i Jalf mają rację w tej sprawie. Z [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>
Ben Voigt

1
Co jest zabawne, ponieważ tak naprawdę myślałem, że zachowanie było zależne od obecności nietrywialnego niszczyciela (w szczególności, że klasy POD można zniszczyć za pomocą wskaźnika do bazy).
Ben Voigt,

3

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ć deleteodbierać ż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):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

W przeciwieństwie do tego zawsze będzie działać (z wirtualnym destruktorem lub bez niego):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

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_ptrlub unique_ptr. W skrócie, NIE deletenic nie dostać bezpośrednio od new.


2

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ć.


2

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!


10
Z mojego doświadczenia wynika, że ​​najgorsze długoterminowe szkody są często powodowane przez ludzi, którzy próbują czegoś źle doradzonego i „ jak dotąd nie doświadczyłem (czytałem zauważyłem ) żadnych problemów z tym”.
Rozczarowany

0

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:

  1. Pozwolę sobie dodać inny sposób pakowania std::vectorbez pisania wielu opakowań funkcji.

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. Dziedziczenie z std :: span zamiast std::vectori unikanie problemu dtor.

0

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ć

  std::vector<T> names; names.reserve(1000);

możemy powiedzieć

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

a następnie, nawet jako członkowie klasy,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(zgodnie z preferencjami) i nie trzeba pisać konstruktora, aby wywołać dla nich rezerwę ().

( reserve_inTechnicznie 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_inlepiej obsługuje darmowy szablon funkcji:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

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:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.