Basisklasse und abgeleitete Klassen in std::vector



  • Hallo!

    Ich möchte einen std::vector<Basisklasse> vBK welchen ich mit abgeleiteten Objekten füllen kann, also z.B. vBK.push_back(AbgeleiteteKlasse()).

    Nun möchte ich Funktionen aufrufen. Möchte ich eine Funktion einer abgeleiteten Klasse aufrufen, so müsste ich casten. Soweit ich weiß wird ja die Subklasse zusammengestutzt auf das Format der Basisklasse, da der vector vom Typ Basisklasse ist.
    Folglich wäre das ja nicht legal: static_cast<AbgeleiteteKlasse>(vBK[0]).FunktionVonAbgeleiteterKlasse() , denn der vector stellt ja nur Speicherplatz für Basisklasse zur Verfügung, folglich haben zusätzliche Attribute der Subklasse keinen Platz und damit können Methoden der Subklasse auch nicht korrekt funtkionieren.

    Nun würde mich erstmal eine Erklärung zu oben beschriebenem interessieren und ob ich da einen Denkfehler drinnen habe.

    Und dann zum zweiten Punkt. Wie realisiere ich es nun am besten, in einem vector vom Typ Basisklasse auch Subklassen unterzubringen. Mit Zeigern? Ist wieder umständlich wegen dem new/delete. Eventuell irgendwas auto_ptr mäßiges?
    Lasst mal hören wie ihr das löst. Bitte um Lösungen die mit C++ und der STL auskommen.



  • Das erste ist richtig. Der Grund ist, dass die Basisklasse im allgemeinen nicht so viel Speicherplatz braucht wie die Basisklasse, deshalb passt die einfach nicht in den Speicher vom vector rein. Deswegen wird der Rest, also das abgeleitete abgeschnitten.

    Zum zweiten:
    Zeiger ist der richtige Ansatz. Um das new/delete zu vermeiden, kann man unique_ptr nehmen (kein auto_ptr, das kann Probleme machen)



  • Danke schon mal.
    unique_ptr scheint ja von C++ 11 zu sein, mal schauen ob das mein Compiler schluckt.
    Wenn nicht, wie wurde das in der Prä-C++ 11 Zeit gehandhabt? Hat man da tatsächlich nackte Pointer verwendet?



  • Frage C++ schrieb:

    Wenn nicht, wie wurde das in der Prä-C++ 11 Zeit gehandhabt? Hat man da tatsächlich nackte Pointer verwendet?

    boost::ptr_vector<T> oder std::vector<boost::shared_ptr<T> >



  • Soll heißen, um boost kommt man also kaum herum wenn man sinnvoll C++ programmieren will...

    Tja, dann werd ich mir boost wohl doch mal auf die Platte schmeißen.



  • Frage C++ schrieb:

    Danke schon mal.
    unique_ptr scheint ja von C++ 11 zu sein, mal schauen ob das mein Compiler schluckt.
    Wenn nicht, wie wurde das in der Prä-C++ 11 Zeit gehandhabt? Hat man da tatsächlich nackte Pointer verwendet?

    Mit boost::shared_ptr . Zur Not kann man sich auch einen einfachen smart_ptr selber schreiben.


  • Mod

    Frage C++ schrieb:

    Soll heißen, um boost kommt man also kaum herum wenn man sinnvoll C++ programmieren will...

    Boost macht auch nix magisches. Einen ptr_vector oder shared_ptr kann man sich an einem Nachmittag auch selber programmieren. Aber dann hat man eben seinen Nachmittag sinnlos verschwendet. Und das Ergebnis wird bei den ersten paar Versuchen sicherlich schlechter sein als das (meistens) recht durchdachte Design von Boost.



  • Welchen Compiler nutzt du denn, der nicht mal unique_ptr kennt?



  • Wenn man dran denkt am Ende zu löschen ist ein roher Pointer doch auch ok. Nie kapiert, was daran so schlimm sein soll...



  • Eisflamme schrieb:

    Wenn man dran denkt am Ende zu löschen ist ein roher Pointer doch auch ok. Nie kapiert, was daran so schlimm sein soll...

    Wenn er es vergisst, selber zu löschen... dann ist es schlimm.



  • Eisflamme schrieb:

    Wenn man dran denkt am Ende zu löschen ist ein roher Pointer doch auch ok. Nie kapiert, was daran so schlimm sein soll...

    Es ist so schlimm. Rohe besitzende Pointer sind das Böse. Ausgeburten der Hölle, Jünger des Fegefeuers, GEZ Angestellte. Ach, sie sind schlimmer als das. Schlimmer als alles was du dir vorstellen kannst!



  • So nen ptr_vector schreibt man nicht an einem Nachmittag, sondern in einer Stunde. Absolut trivial.

    Einfach einen vector wrappen und jede Löschaktion mit einem operator delete Aufruf ausschmücken, fertig.



  • Frage C++ schrieb:

    Ich möchte einen std::vector<Basisklasse> vBK welchen ich mit abgeleiteten Objekten füllen kann,

    Das ist ein Widerspruch!

    Frage C++ schrieb:

    ...
    Nun würde mich erstmal eine Erklärung zu oben beschriebenem interessieren und ob ich da einen Denkfehler drinnen habe.

    Das ist korrekt so.

    Frage C++ schrieb:

    Und dann zum zweiten Punkt. Wie realisiere ich es nun am besten, in einem vector vom Typ Basisklasse auch Subklassen unterzubringen. Mit Zeigern? Ist wieder umständlich wegen dem new/delete.

    Mit Zeigern oder so etwas ähnlichem wie Zeiger. Wie sonst?

    Frage C++ schrieb:

    Lasst mal hören wie ihr das löst. Bitte um Lösungen die mit C++ und der STL auskommen.

    Kommt echt drauf an. Neulich hab ich's so gemacht:

    class PolymorphicBase
    {
    public:
      virtual ~PolymorphicBase() {}
    
      und so weiter
    };
    
    und so weiter
    
    boost::ptr_vector<PolymorphicBase> vec;
    vec.push_back(new Derived("bla",23.0,42));
    

    Du kannst aber auch in C++11 'nen vector mit unique_ptr-Objekten befüllen...



  • cooky451 schrieb ironisch:

    > Es ist so schlimm. Rohe besitzende Pointer sind das Böse. [...]

    Jain. Je kleiner der Rahmen, desto besser und fehlerunanfälliger. Z.B. innerhalb eines unique_ptr s oder ptr_vector s geht's ja nicht anders. ^^ Und sonst sehe ich aber auch nicht wirklich einen Sinn darin, boost::ptr_vector nachzubauen ...



  • Das war nicht wirklich ironisch gemeint. Mit rohen Zeigern meinte ich natürlich alles außerhalb einer Smartpointer(-ähnlichen) Klasse. 🙂

    Und nicht, wie viele vielleicht vermuten mögen, weil man das vergessen könnte. Nein, da muss man zwar drauf achten, aber es gibt viel schlimmere Dinge:

    void foo()
    {
      A* a = new A;
      bar(); // BAM, Exception, Fail.
      delete a;
    }
    
    class C
    {
      A* a_;
    public:
      C()
        : a_(new A)
      {
        bar(); // BAM, Exception, Fail.
      }
      ~C()
      {
        delete a_;
      }
    };
    

    Deswegen halte ich es tatsächlich für grundsätzlich nicht vertretbar, besitzende Zeiger außerhalb von Smartpointern zu halten.



  • krümelkacker schrieb:

    Und sonst sehe ich aber auch nicht wirklich einen Sinn darin, boost::ptr_vector nachzubauen ...

    Ich auch nicht, aber wenn man den nicht zur Verfügung hat, geht es nicht anders.

    krümelkacker schrieb:

    cooky451 schrieb ironisch:

    > Es ist so schlimm. Rohe besitzende Pointer sind das Böse. [...]

    Jain. Je kleiner der Rahmen, desto besser und fehlerunanfälliger.

    Ungefähr so klein wie der folgende?

    cooky451 schrieb:

    Und nicht, wie viele vielleicht vermuten mögen, weil man das vergessen könnte. Nein, da muss man zwar drauf achten, aber es gibt viel schlimmere Dinge:

    void foo()
    {
      A* a = new A;
      bar(); // BAM, Exception, Fail.
      delete a;
    }
    

    Die sehr umständliche Fehlerbehandlung, die man mit RAII nicht benötigt:

    A* a = new A;
    try
    {
    	bar();
    }
    catch (...)
    {
    	delete a;
    	throw;
    }
    delete a;
    

    Das würde ich nicht vergessen nennen. Eher verwechseln und zwar mit C, wo es keine Ausnahmen gibt. C++ wird überhaupt sehr oft mit C verwechselt. Im Nachhinein hätte man C++ ganz anders nennen müssen, weil es nichts mehr mit C zu tun hat. Ist so ähnlich wie bei Java und JavaScript.
    Wenn C++ nicht aus C entstanden wäre, sondern von Anfang an als C++11 mit anderem Namen erschienen wäre, würde niemand auf die Idee kommen, delete jemals aufzurufen, weil die Assoziation mit free und Konsorten nicht so stark wäre.



  • Artchi schrieb:

    Eisflamme schrieb:

    Wenn man dran denkt am Ende zu löschen ist ein roher Pointer doch auch ok. Nie kapiert, was daran so schlimm sein soll...

    Wenn er es vergisst, selber zu löschen... dann ist es schlimm.

    Primäres Argument für mich ist nicht das Vergessen, sondern Exception-Sicherheit...


  • Mod

    Hier ein Beispiel für die Smartpointeverweigerer, das sich nicht mehr mit try-catch wegreden lässt, selbst mit dem weitgehend unbekannten function-try-block:

    #include <memory>
    #include <exception>
    
    class success {};
    class fail
    {
    public:
      fail() {throw std::exception();}
    };
    
    class naives_raii
    {
      success *s;
      fail *f;
    public:
      naives_raii(): s(new success), f(new fail) {}
      ~naives_raii() {delete f; delete s;}
      naives_raii(const naives_raii&) = delete;
      naives_raii operator=(const naives_raii&) = delete;
    };
    
    template<typename T> class smartpointer
    {
      T* data;
    public:
      smartpointer(T* t): data(t) {}
      ~smartpointer() {delete data;}
      smartpointer(const smartpointer&) = delete;
      smartpointer operator=(const smartpointer&) = delete;
    };
    
    class richtiges_raii
    {
      smartpointer<success> s;
      smartpointer<fail> f;
    public:
      richtiges_raii(): s(new success), f(new fail) {}
    };
    
    int main()
    {
      try
        {
          richtiges_raii r;  // Alles ok
        }
      catch(std::exception) {}
    
      try
        {
          naives_raii n; // Oh nein, Speicherleck!
        }
      catch(std::exception) {}
    }
    


  • Und new selbst darf ja auch noch werfen, nicht vergessen. Sehr schöner Fail ist z.B.

    my_type t(new A, new B);
    

    Wenn das letztallokierte wirft, gibt's nen Speicherleck.

    Bald können wir eine FailWithoutSmartpointersPedia aufmachen. 🤡



  • Da waren die C++ Programmierer ja echt arm dran vor 2011. Na ein Glueck gab es Java.

    Wie oft passiert es, dass man mit new 'ne Exception bekommt und weitermachen moechte? Bei mir hat new noch nie eine Exception geworfen. Aber ich bin bestimmt die Ausnahme.


Anmelden zum Antworten