Operator new[] für abgeleitete Klassen



  • @RudiMBM
    Im kann deinen Einsatzzweck überhaupt nicht nachvollziehen. Mache bitte mal ein kleines Beispiel.

    Der Speicherblock kann bei new[180000] mit je min.600 Byte groß werden.

    Heißt das du hast immer 180000 GenericSubClass-Instanzen, deren Größe mindestens 600 Byte groß sind?

    Warum nutzt du überhaupt new[]? Warum funktioniert hier kein std::vector, std::set, std::map?

    Ich will zu einem Zeitpunkt nur eine SubClassArt haben, danach kann der speicher freigegeben werden.

    Was meinst du damit? Willst du z.B. ein Element in Elements löschen können?



  • @RudiMBM sagte in Operator new[] für abgeleitete Klassen:

    @Belli Geht schon... macht aber den selben Fehler.

    Welchen?



  • Damit es klar wird:

    class C_Rechnen
    {
    public:
    							C_Rechnen();
    virtual						~C_Rechnen() = 0;
    virtual     void	    	NeueAuswertung( );
    virtual     void	    	Erstellen( int ) = 0;
    virtual     void	    	TestIter( ) = 0;
    			long double		innermax, innermin, outermax, outermin;
    			bigint			ax, ay;
    			int				itercount;
    };
    //------------------------------------------------------------
    class C_RechnenMB : public C_Rechnen
    {
    public:
    							C_RechnenMB();
    							~C_RechnenMB();
    			void*			operator new[]( size_t C_RechnenMB ) { return malloc( C_RechnenMB ); };
    
    			void	    	NeueAuswertung( );
    			void			Erstellen( int );
    };
    /------------------------------------------------------------
    class C_RechnenBB : public C_Rechnen
    {
    public:
    							C_RechnenMB();
    							~C_RechnenMB();
    			void*			operator new[]( size_t C_RechnenMB ) { return malloc( C_RechnenMB ); };
    
    			void	    	NeueAuswertung( );
    			void			Erstellen( int );
    private:
    			Weitere lokale Daten
    };
    /------------------------------------------------------------
    ..... weitere subclassen wie oben
    
    
    Anwendung;
    .......
    C_Rechnen   *ErgAnz, *ErgAnzArray;
    
    .....
    	switch ( Mode ) {
    		case 1:		ErgAnzArray = new C_RechnenMB[Anzahl];	break;
    		case 2:		ErgAnzArray = new C_RechnenBB[Anzahl];	break;
    		...
    		default :	ErgAnzArray = new C_Rechnen??[Anzahl];	break;
    		}
    
    .......
    ErgAnz = ErgAnzArray;
    for ( ky=0; ky<bsesy; ky++ ) {
    	for ( kx=0; kx<bsesx; kx++ ) {
    		ErgAnz->NeueAuswertung();
    		ErgAnz++;
    		}
    	}
    
    ErgAnz = ErgAnzArray;
    for ( ky=0; ky<bsesy; ky++ ) {
    	for ( kx=0; kx<bsesx; kx++ ) {
    		ErgAnz->Erstellen( 123 );
    		ErgAnz++;
    		}
    	}
    ....
    delete[] ErgAnzarray;
    ...
    @Belli 
    Wenn ich nun in der Anwendung den ErgAnz++ iteriere, dann ist das nächste Element immer nur mit dem Displacement der Basisklasse weitergerechnet.
    Das wäre zu korrigieren! ..denn dann schlägt die Exception zu!


  • Hast du dir den Quelltext mal angeschaut? Bitte gib dir Mühe und formatier´ ihn so, dass man ihn auch lesen kann.

    Dein echtes Problem ist aber, wie schon oben beschrieben, Object Slicing. Du erstellst zB ein Array aus C_RechnenMB und weist das einem C_Rechnen Pointer zu. Mit der Zuweisung verlierst du die Information über den korrekten Datentyp. Das hast du schon richtig bemerkt, dass der Zeiger bei der Pointerarithmetik auf ungültige Elemente zeigt, weil die Größe eines C_Rechnen-Objekts nicht der Größe eines C_RechnenMB entspricht.

    Beispiel:
    Klasse Base hat eine Größe von 16 Byte, Klasse Derived hat eine Größe von 24 Byte und ist von Base abgeleitet:

    Base* arr = new Derived[10]; // arr ist gültig und zeigt auf den korrekten Derived
    arr++;                       // der arr-Zeiger wird um die Größe von Base vergrößert (16Byte) und zeigt mitten in das erste Derived-Objekt
    

    Das Problem lässt sich auch nicht durch eine operator new Überladung lösen, sondern ist ein Designfehler. Wenn das Array immer von einem bestimmten Typ ist kannste das vllt über separate Funktionen lösen (vllt geht statischer Polymorphismus per templates), oder indem du jedes konkrete C_Rechnen Objekt auf dem Heap erzeugst. Die zweite Option würde ich vermeiden, wenn´s irgendwie anders geht.

    PS:
    Als Hack könntest du der C_Rechnen Klasse eine C_Rechnen* advance( C_Rechnen* current )Funktion spendieren, die den übergebenen Pointer um den korrekten Offset verschiebt:

    class C_Rechnen
    {
       virtual C_Rechnen* advance( C_Rechnen* current ) const = 0;
    };
    
    class C_RechnenMB
    {
       C_Rechnen* advance( C_Rechnen* current ) const override
       {
          // Pointer wird um die Größe der konkreten Klasse verschoben
          C_RechnenMB* tmp = dynamic_cast<C_RechnenMB*>( current );
          return tmp ? ++tmp : nullptr;
       }
    }
    
    // Pointer auf nächstes Element wird durch das Element selbst bestimmt.
    C_Rechnen* arr = new C_RechnenMB[2];
    arr = arr->advance( arr );
    

    Ist aber nur ne Krücke, die ich im Produktivcode nicht benutzen würde.

    Beispiel für statischen Polymorphismus per templates:

    template<typename Calculator>
    ... calculate( ... )
    {
       std::vector<Calculator> calculators( count );
       for( auto& c : calculators ) c.NeueAuswertung(); // kann man sich sparen, wenn man das bereits im Konstruktor tut
       for( auto& c : calculators ) c.Erstellen( 123 );
       return was_auch_immer;
    }
    
    ... calculate_results( ... )
    {
       if( mode == 1 )       return calculate<C_RechnenMB>( ... );
       else if( mode == 2 )  return calculate<C_RechnenBB>( ... );
       default :             return calculate<C_Rechnen??>( ... );
    }
    


  • @RudiMBM abgesehen davon, dass die Operatorüberladung garantiert nicht nötig ist, macht new[] in C_RechnenBB ein malloc( C_RechnenMB ) das sieht an der Stelle nach einem Copy & Paste Fehler aus.



  • @RudiMBM. Da ErgAnz (bzw. ErgAnzArray) ein C_Rechnen * ist, wird bei ErgAnz++ immer nur um sizeof(C_Rechnen) der Zeiger erhöht.
    Du müßtest einen char * verwenden und um sizeof(Subclass) (eigene Variable ' size' dafür verwenden) und dann nach += size wieder in den Basisklassenzeiger casten.

    Ist aber, wie auch @DocShoe schon geschrieben hat, ein Designfehler.
    Alternative wäre ein C_Rechnen **, d.h. ein Array von C_Rechnen * und d.h. wie auch schon von @DocShoe geschrieben, in modernem C++ ein std::vector<std::unique_ptr<C_Rechnen>>.



  • vielleicht:

    std::vector<*C_Rechnen> ErgAnz;
    for(size_t i = 0; i < Anzahl; ++i)
       ErgAnz.push_back(new C_RechnenMB/*C_RechnenBB ...*/);
    

    Edit: Okay, bin zu spät ...



  • @Belli sagte in Operator new[] für abgeleitete Klassen:

    std::vector<*C_Rechnen> ErgAnz;
    

    Ist das neues C++? 🙂



  • Ich glaube, das ist gebraucht ...
    Was gefällt Dir daran nicht, ist das falsch?

    Edit:
    Ja, ist falsch .... 🙃
    Es muss natürlich:

    std::vector<C_Rechnen*> ErgAnz;
    

    heißen ...
    Tschuldigung, aber ich bin keine 60 mehr ...



  • @Belli sagte in Operator new[] für abgeleitete Klassen:

    Tschuldigung, aber ich bin keine 60 mehr ...

    😱
    Sprich mich an, ich helfe dir über die Straße.



  • @DocShoe Und wer bringt nun mich über die Straße?



  • @RudiMBM sagte in Operator new[] für abgeleitete Klassen:

    @DocShoe Und wer bringt nun mich über die Straße?

    Das hat er doch bereits! Das folgende Beispiel von DocShoe finde ich gut.

    Beispiel für statischen Polymorphismus per templates:

    template<typename Calculator>
    ... calculate( ... )
    {
      std::vector<Calculator> calculators( count );
      for( auto& c : calculators ) c.NeueAuswertung(); // kann man sich sparen, wenn >man das bereits im Konstruktor tut
      for( auto& c : calculators ) c.Erstellen( 123 );
      return was_auch_immer;
    }
    
    ... calculate_results( ... )
    {
      if( mode == 1 )       return calculate<C_RechnenMB>( ... );
      else if( mode == 2 )  return calculate<C_RechnenBB>( ... );
      default :             return calculate<C_Rechnen??>( ... );
    }
    


  • @Quiche-Lorraine
    @DocShoe
    Sorry, sowas hab noch nie gebraucht und nie gesehen. Somit verstehe ich (noch) nicht was da abläuft (bis auf vector). Auch hab ich überhaubt keinen Plan wie ich dies in meine Anwendung einbauen könnte.
    Ich verstehe auch nicht warum meine Version mit einem Array von Klassen nicht funktioniert. In der Vorgängerversion hab ich die KlassenPtr genauso verwendet und der Ptr auf die Basiklasse hat auch funktioniert, nur habe ich alle Instanzen einzeln nacheinander erzeugt und wieder deleted. Und das scheint mir auch logisch im Classenkonzept. Denn wenn ich zB "Shape->Draw();" programmiere, dann benutze ich ja auch den Basisptr der Shapeklasse.



  • @RudiMBM sagte in Operator new[] für abgeleitete Klassen:

    Sorry, sowas hab noch nie gebraucht und nie gesehen. Somit verstehe ich (noch) nicht was da abläuft (bis auf vector). Auch hab ich überhaubt keinen Plan wie ich dies in meine Anwendung einbauen könnte.

    Dann wird es Zeit dass du ein Testprojekt generierst und dich intensiv mit der STL und Templates beschäftigst.


    Dein Code erscheint mir insgesammt zu C lastig. Alleine von der folgenden Zeile stellen sich schon meine Nackenhaare.

    void* operator new[]( size_t C_RechnenMB ) { return malloc( C_RechnenMB ); };
    

    So einen Code ist höchst fehleranfällig und ist in C++ nur noch selten notwendig.



  • @RudiMBM sagte in Operator new[] für abgeleitete Klassen:

    Ich verstehe auch nicht warum meine Version mit einem Array von Klassen nicht funktioniert. In der Vorgängerversion hab ich die KlassenPtr genauso verwendet und der Ptr auf die Basiklasse hat auch funktioniert, nur habe ich alle Instanzen einzeln nacheinander erzeugt und wieder deleted. Und das scheint mir auch logisch im Classenkonzept. Denn wenn ich zB "Shape->Draw();" programmiere, dann benutze ich ja auch den Basisptr der Shapeklasse.

    Das haben TH69 und ich doch schon ausführlich erklärt. Der nicht-Pointer Ansatz kann nicht funktionieren, da du einen Zeiger der Basisklasse auf das erste Arrayelement zeigen lässt und der der Zeiger bei der Inkrementierung halt nur um die Größe der Basisklasse verschoben wird, und wenn die abgeleitete Klasse größer als die Basisklasse ist, dann steht dein Zeiger iwo im Speicher.
    Wenn du alle Objekte separat auf dem Heap erzeugst hast du ein Array aus Zeigern. Zeiger sind immer gleich groß, egal, auf was sie zeigen, daher funktioniert die Zeigerarithmetik.



  • @Quiche-Lorraine sagte in Operator new[] für abgeleitete Klassen:
    ...

    Blödsinn gelöscht



  • @RudiMBM
    Du kannst deinen Speicher selber allozieren und verwalten, wenn du unbedingt möchtest, sollte man aber vermeiden und ist in C++ so nicht mehr nötig. Selbst in "altem" C++ sollte man das wegkapseln (Stichwort RAII).

    Warum iterierst du über den ErgAnz Pointer? Warum nicht von 0 - Anzahl

    for(unsigned int i = 0; i<Anzahl; ++i)
    {
       ErgAnz[i].NeueAuswertung();
    }
    


  • @DocShoe ist klar. Nur wenn ich die Objekte einzeln Erzeuge, kann ich mir nicht sicher sein, dass sie ohne Lücken im Heap stehen. Evtl. wollte ich das ganze array auf Platte stremen.



  • @Schlangenmensch Macht den gleichen Fehler wie Ptr++,, das hatte ich schon vorher probiert.



  • @RudiMBM sagte in Operator new[] für abgeleitete Klassen:

    @DocShoe ist klar. Nur wenn ich die Objekte einzeln Erzeuge, kann ich mir nicht sicher sein, dass sie ohne Lücken im Heap stehen. Evtl. wollte ich das ganze array auf Platte stremen.

    Das dürfte aus den gleichen Gründen wie oben sowieso nicht funktionieren. Mit der Zuweisung C_Rechnen* ErgAnzArray = new C_RechnenMB[100] verlierst du sämtliche Informationen über den konkreten Datentyp! D.h., du kannst nicht mal mehr bestimmen, wie groß das Array im Speicher wirklich ist (gemessen in Bytes). Und damit können auch so Sachen wie fwrite( ErgAnzArray, Elementgröße?, anzahl, handle ) nicht funktionieren. Abgesehen davon, dass das so oder so kein gutes I/O Datenhandling ist.

    Du verrennst dich da in etwas, das definitiv in einer Sackgasse endet.


Anmelden zum Antworten