Mal wieder ein "pure virtual function call"



  • Hallo SeppJ, hallo 3141....

    Danke für die schnellen Antworten.

    Ohne Code kann man nix genaues sagen.

    Das verstehe ich als Aufforderung 😉

    Was sollen denn die ganzen Pointer?

    Die liste (oder vector) soll wirklich nur pointer beinhalten. Die Objekte werden irgendwo erzeugt (und von irgendwem besessen) und sollen künfitg vom GridBuffer über Änderungen informiert werden. dazu rufen sie dann ein

    GridBuffer.addListener(this)
    

    auf und sind somit als Listener eingetragen. Diese Liste soll die Objekte nicht "beeihalten"!

    Also ich hab jetzt erstmal versucht, einen assign operator und einen copy constructor für R7GridBuffer einzubauen, damit ich die Regel der großen Drei nicht verletze.
    Ich habe den Destructor von R7GridBufferListener als virtual deklariert, warum das so sein muss, habe ich (glaube ich) verstanden.

    Ich poste meinen Code jetzt mal hier, denn da sind sicher noch einige Fehler drin.

    #ifndef __r7gridbuffer__
    #define __r7gridbuffer__
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <vector>
    #include <iostream>
    using namespace std; 
    
    class R7GridBuffer;
    
    enum R7BufferChange
    {
    	cellChange,
    	rowChange,
    	columnChange,
    	generalChange
    };
    
    class R7GridBufferListener 
    {
    public:
    	R7GridBufferListener();
    	virtual ~R7GridBufferListener();
    
    	virtual void bufferChanged(R7GridBuffer* buffer, R7BufferChange change, int arg1, int arg2, char* arg3) = 0;
    };
    
    class R7GridBuffer
    {
    public:
    	R7GridBuffer();
    	R7GridBuffer(R7GridBuffer &copy);
    	R7GridBuffer& operator= (const R7GridBuffer& assign);
    	virtual ~R7GridBuffer();
    
    	void setSize(int size_x, int size_y);
    	int  getSizeX();
    	int  getSizeY();
    	void clear();
    
    	bool  setCell(int x, int y, char value);
    	char  getCell(int x, int y);
    
    	bool  setRow(int row, char* values);
    	char* getRow(int row);
    
    	bool  setColumn(int column, char* values);
    	char* getColumn(int column);
    
    	bool addListener(R7GridBufferListener* l);
    
    private:
    	int m_x, m_y;
    	char** data;
    
    	char* m_informdata;
    	int m_size_informdata;
    
    	vector<R7GridBufferListener*> m_listeners;
    	int m_num_listeners;
    
    	void informListeners(R7BufferChange change, int arg1, int arg2);
    };
    

    Dazu noch die *.cpp Datei:

    #ifndef __r7gridbuffer__
    #include "r7gridbuffer.h"
    #endif
    
    R7GridBufferListener::R7GridBufferListener()
    {
    
    }
    
    ///////////////////////////////////////////////////////////////
    R7GridBuffer::R7GridBuffer()
    {
    	m_x = 0;
    	m_y = 0;
    	data = 0;
    	m_num_listeners = 0;
    	m_size_informdata = 0;
    }
    
    R7GridBuffer::R7GridBuffer(R7GridBuffer &copy)
    {
    	m_x = copy.getSizeX();
    	m_y = copy.getSizeY();
    
    	data = new char *[m_x];
    	for (int i = 0; i<m_x; i++)
    	{
    		data[i] = new char[m_y];
    		for (int j= 0; j<m_y; j++)
    		{
    			data[i][j] = copy.getCell(i,j);
    		}
    	}
    	m_num_listeners = 0;
    }
    
    R7GridBuffer& R7GridBuffer::operator= (const R7GridBuffer &assign)
    {
    	m_x = assign.m_x;
    	m_y = assign.m_y;
    
    	if (data != 0)
    	{
    		for (int i = 0; i<m_x; i++)
    		{
    			delete data[i];
    		}
    		delete data;
    
    		data = 0;
    	}
    
    	data = new char *[m_x];
    	for (int i = 0; i<m_x; i++)
    	{
    		data[i] = new char[m_y];
    		for (int j= 0; j<m_y; j++)
    		{
    			data[i][j] = assign.data[i][j];
    		}
    	}
    	return *this;
    }
    
    R7GridBuffer::~R7GridBuffer()
    {
    	if (data != 0)
    	{
    		for (int i = 0; i<m_x; i++)
    		{
    			delete data[i];
    		}
    		delete data;
    
    		data = 0;
    	}
    
    	delete m_informdata;
    }
    
    void R7GridBuffer::setSize(int size_x, int size_y)
    {
    	if (data != 0)
    	{
    		for (int i = 0; i<m_y; i++)
    		{
    			delete data[i];
    		}
    		delete data;
    
    		data = 0;
    	}
    
    	m_x = size_x;
    	m_y = size_y;
    	data = new char *[m_x];
    	for (int i = 0; i<m_y; i++)
    	{
    		data[i] = new char[m_y];
    	}
    }
    
    int R7GridBuffer::getSizeX()
    {
    	return m_x;
    }
    
    int R7GridBuffer::getSizeY()
    {
    	return m_y;
    }
    
    void R7GridBuffer::clear()
    {
    	for (int i = 0; i<m_x; i++)
    		for (int j = 0; j<m_y; j++)
    		{
    			data[i][j] = 0;
    		}
    }
    
    bool  R7GridBuffer::setCell(int x, int y, char value)
    {
    	if ((x >= m_x) || (y >= m_y))
    		return false;
    	data[x][y] = value;
    
    	informListeners(cellChange,x,y);
    	return true;
    }
    
    char  R7GridBuffer::getCell(int x, int y)
    {
    	if ((x >= m_x) || (y >= m_y))
    		return -1;
    	return data[x][y];
    }
    
    bool  R7GridBuffer::setRow(int row, char* values)
    {
    	if (row >= m_y)
    		return false;
    	for (int i=0; i<m_x; i++)
    	{
    		data[i][row] = values[i];
    	}
    
    	informListeners(rowChange,row,m_x);
    	return true;
    }
    
    char* R7GridBuffer::getRow(int row)
    {
    	if (row >= m_y)
    		return false;
    
    	char* ret = new char[m_x];
    
    	for (int i=0; i<m_x; i++)
    	{
    		ret[i] = data[i][row];
    	}
    
    	return ret;
    }
    
    bool  R7GridBuffer::setColumn(int column, char* values)
    {
    	if (column >= m_x)
    		return false;
    	for (int i=0; i<m_y; i++)
    	{
    		data[column][i] = values[i];
    	}
    
    	informListeners(columnChange,column,m_y);
    	return true;
    }
    
    char* R7GridBuffer::getColumn(int column)
    {
    	if (column >= m_x)
    		return false;
    
    	char* ret = new char[m_y];
    
    	for (int i=0; i<m_x; i++)
    	{
    		ret[i] = data[column][i];
    	}
    
    	return ret;
    }
    
    bool R7GridBuffer::addListener(R7GridBufferListener* l)
    {
    	if (l == nullptr)
    		return false;
    	m_listeners.push_back(l);
    	return true;
    }
    
    void R7GridBuffer::informListeners(R7BufferChange change, int arg1, int arg2)
    {
    // hier muss noch viel rein
    }
    

    Der Compiler übersetzt das. Puh.

    Übrigens: z.B. bei R7GridBuffer::getRow(): Ich bin mir fast schon sicher, dass das mit dem Rückgabewert so nicht geht... Wird das array, auf das der pointer zeigt nach verlassen der Methode gelöscht? bleibt es bestehen? Wie löst man das, wenn man ein array zurückgeben will?

    Danke tausendmal!
    StrangeMan


  • Mod

    Nein, das wird nicht zerstört, klassisches Speicherleck. new[] brauchst du nie! Nimm stattdessen immer vector!



  • Ich muss gestehen, egal, was ich versuche, es wird nicht besser...

    Mal eine ganz generelle Frage: Ich habe ein Object "Base", welches zwei Objekte "Child1" und "Child2" beinhaltet. Child2 braucht für die Initialisierung eine Referenz auf Child1. Wie kann ich die Initialisierung machen, wenn das ungefähr so aussieht:

    class Child1
    {
    public:
        Child1();
        ~Child1();
    };
    
    class Child2
    {
    public:
        Child2(const Child1 &reference);
        ~Child2();
    };
    
    class Base
    {
    public:
        Child1 c1;
        Child2 c2;
    
        Base();
        ~Base();
    };
    

    Bisher habe ich das ja mit den Pointern gelöst, was (wie ich jetzt weiß) eine dumme Idee ist. Mein Ansatz wäre jetzt der hier:

    Base::Base()
        :c1(), c2(c1)
    {
    
    }
    

    Mein Compiler nimmt das so hin, aber beim Starten freezt das Programm. Ich suche jetzt schon eine ganze Weile, wie man bei composition richtig initialisiert (z.B. wenn es keinen standard-constructor gibt).
    Danke,
    StrangeMan

    PS: Wozu brauche ich das? z.B. gibt es bei Juce einen FileLogger. Ich würde gerne Loggen, was mein programm macht, zum debuggen. Dieser Logger ist ebenfalls Member meiner Base-Class muss als erstes fertig konstruiert sein, damit andere objecte meienr Base-Class darüber Meldungen ausgeben können. Mir kommt so langsam der Verdacht, dass das nicht gehen wird. Nur wie geht das dann?!



  • Hey man, thats strange, Base und Child für Klassen zu Verwenden, bei denen Vererbung gar keine Rolle spielt. Außerdem hast du kein Objekt "Base", sondern eine Klasse "Base", die jeweils eine Instanz (also ein Objekt) der Klassen "Child1" und "Child2" als Membervariable beinhaltet. Bist du sicher, dass es das ist, was du willst bzw brauchst? 🙂



  • Hallo

    Nagut, Base und Child sind vielleicht etwas missverständlich.
    Mir ist schon klar, dass ich kein "Objekt" Base habe, sondern eine Klasse, von der ich ein Objekt erzeugen kann. In meinem Fall gibt es aber mit Sicherheit nur ein Objekt dieser Klasse. Und dass Child1 und Child2 Membervariablen sind, weiß ich doch.

    Bist du sicher, dass es das ist, was du willst bzw brauchst? 🙂

    Ja, ich denke schon. JUCE stellt mir zur Programmierung eines VST-Plugins zwei Klassen zur Verfügung, die vom VST-Host aufgerufen werden (genaugenommen eine). Innerhalb dieser zwei muss ich alles "unterbringen", was ich für meine Anwendung brauche, richtig? Also werde ich nicht drumrumkommen, mir diese zwei zur Verfügung stehenden Klassen mit Memberobjekten zu füllen. Und meine Frage war, wie ich die dann korrekt initialisieren kann, wenn sie sich untereinander referenzieren, bzw. aufeinander zugreifen müssen. Siehe das Beispiel mit dem Logger aus meinem letzten Post. Vielleicht kannst du mir das ja beantworten.



  • Dann wars vermutlich so gemeint, oder?

    class Logger
    {
    public:
        Logger();
        ~Logger();
    };
    
    class Macher
    {
    public:
        Macher(const Logger& logger);
        ~Macher();
    };
    
    class Rahmer
    {
    public:
        Rahmer();
        ~Rahmer();
    private:
        Macher macher_;
        Logger logger_;
    };
    

    Ich denke, ich würde den Logger gar nicht als Referenz übergeben, denn wenn dein Macher beispielsweise in deinem dtor noch irgendwas loggen will, musst du sicherstellen, dass deine Logger-Instanz zu dem Zeitpunkt noch nicht zerstört ist, sonst zeigt deine Referenz irgendwohin und du hast UB.
    Wenn dein Macher der einzige ist, der den Logger braucht, könntest du den Logger als Member direkt in den Macher stecken.
    Wenn der Rahmer ihn aber auch braucht, könntest den deinen Macher so gestalten, dass er im ctor noch keinen Logger braucht sondern eine Funktion

    Macher::SetLogger( std::shared_ptr<Logger> logger );
    

    anbietet, die du dann im ctor-Rumpf von Rahmer befüllst (und schön RAII benutzt).

    typedef std::shared_ptr<Logger> LoggerPtr;
    class Macher
    {
    public:
        SetLogger( LoggerPtr loggerPtr );
    };
    class Rahmer
    {
    public:
        Rahmer();
    private:
        Macher macher_;
        loggerPtr loggerPtr_;
    };
    
    Rahmer::Rahmer() : loggerPtr_( new Logger )
    {
        macher_.SetLogger( loggerPtr_ );
    }
    

    So können Rahmer und Macher loggen und der Logger lebt auf jeden Fall noch in den dtoren.
    Gibt bestimmt noch ganz viele andere Möglichkeiten (einige davon sicher auch hübscher als das), aber da ich mich weder mit JUCE, VST oder dem, was du vorhast, auskenne, kann ich nur ins Blaue raten. 😉



  • Hallo!

    Ja, das mit dem shared_ptr ist eine gute Idee! Danke, ich werde mal damit herumprobieren.
    Aber sehe ich das richtig:

    Rahmer::Rahmer() : loggerPtr_( new Logger )
    {
        macher_.SetLogger( loggerPtr_ );
    }
    

    Bevor macher_.SetLogger aufgerufen wird, ist der ctor von macher ja schon durchgelaufen. Da bekomme ich also nichts geloggt. Was wäre, wenn ich das so mache:

    Rahmer::Rahmer() : loggerPtr_( new Logger ), macher(loggerPtr_)
    {
    }
    

    (soll heißen, ich übergebe die den shared_ptr auf den Logger dem ctor von macher). Wenn ich das probiere, gibt's wieder einen schönen Freeze... (wieso eigentlich?)

    Danke trotzdem für den guten Hinweis! Das brignt mich schon wieder ein Stück voran!
    StrangeMan



  • Ja, im Prinzip ist das richtig, was du da machst, wenn du schon im ctor von Macher loggen willst.

    Allerdings ist zu beachten, dass dein Logger-Objekt schon fertig sein muss wenn der Macher konstruiert wird. Die Reihenfolge, in der deine Member initialisiert werden, ist nicht die, in der sie in der member initialization list auftauchen, sondern die, in der sie in der Klassen-Deklaration stehen.

    Und so funktionierts dann auch:

    #include <memory>
    #include <iostream>
    
    class Logger
    {
    public:
        void Log( const std::string& entry );
    };
    typedef std::shared_ptr<Logger> LoggerPtr;
    
    void Logger::Log( const std::string& entry )
    {
        std::cout << entry << "\n";
    }
    
    class Macher
    {
    public:
        Macher( LoggerPtr loggerPtr );
    private:
        LoggerPtr loggerPtr_;
    };
    
    Macher::Macher( LoggerPtr loggerPtr ) :
            loggerPtr_( loggerPtr )
    {
        loggerPtr_->Log( "Macher::Macher" );
    }
    
    class Rahmer
    {
    public:
        Rahmer();
    private:
        LoggerPtr loggerPtr_; // loggerPtr_ muss vor macher_ fertig sein,
        Macher macher_;       // weil macher_ den loggerPtr_ in seinem ctor benutzt.
    };
    
    Rahmer::Rahmer() : loggerPtr_( new Logger ), macher_( loggerPtr_ )
    {
        loggerPtr_->Log( "Rahmer::Rahmer" );
    }
    
    int main()
    {
        Rahmer rahmer;
    }
    

    Ausgabe:

    Macher::Macher
    Rahmer::Rahmer
    


  • Die Reihenfolge, in der deine Member initialisiert werden, ist nicht die, in der sie in der member initialization list auftauchen, sondern die, in der sie in der Klassen-Deklaration stehen.

    Ahhhh!, superb, jetzt geht's! Na da hätte man erstmal drauf kommen müssen. Wunderbar, wieder was entscheidendes gelernt! Danke!

    Ich habe mein Plugin jetzt soweit "neu aufgebaut" und bin so ziemlich alle Pointer durch shared_ptr und stl-container und dergleichen losgeworden. DAs klappt soweit wunderbar, im Logfile sehe ich auch sehr schön, was wann und wie erzeugt und zerstört wird.

    Jetzt bin ich aber dummerweise wieder bei eben jenem Problem, mit dem ich den Thread begonnen habe. Ich kürze mal zusammen:
    Ich habe die abstrakte base class "R7GridBufferListener". Die soll ihre Eigenschaften an Kind-Klassen vererben damit man über einen Pointer R7Grudbuffer* ptr mal verschiedene Klassen über die geerbte Methode aufrufen kann.
    Das Ganze sieht in etwa so aus:

    +------------------------------+
    | R7GridBufferListener         |=============|
    |   => void bufferChanged(..); |             ||
    +------------------------------+             ||
             ||                                  ||
             || vererbt an                       || vererbt an
             \/                                  \/
    +-------------------------------+     +-------------------------------+ 
    | R7Core:                       |     | R7MonomeView:                 |
    |   public R7GridBufferListener |     |   public Component,           |
    +-------------------------------+     |   public R7GridBufferListener |
                                          +-------------------------------+
    

    Nun werden je ein Objekt von R7Core und eins von R7MonomeView instanziiert. Das klappt auch und bei beiden wird (wie zu erwarten) der ctor von R7GridBufferListener aufgerufen.
    Wenn ich mir jetzt einige Pointer baue:

    vector<R7GridBufferListener*> m_listeners;
    

    und darin je einen Pointer auf die beiden Objekte von R7Core und R7MonomeView speichere

    m_listeners.push_back(this);
    

    dann kann ich die geerbte Methode vom R7Core-Objekt und vom R7Monomeview-Objekt folgendermaßen aufrufen:

    for (unsigned int i=0; i<m_listeners.size(); i++)
    	{
    			m_logger->logMessage("informing listener: " + String(i));
    			m_listeners.at(i)->bufferChanged(this, change, arg1, arg2, *m_informdata.get());
    	}
    

    Für das Objekt von R7Core klappt das auch. Beim Objekt vom R7Monomeview gibt's einen Absturz. Liegt das daran, dass ich multiple inheritance betreibe? Wie behebe ich das?

    Dankeschön für eure Hilfe!
    StrangeMan

    Edit: Ich habe bewusst nur sehr wenig code gepostet. Wenn das Problem so nicht erkennbar ist, poste ich natürlich gerne den gesamten code.



  • Dass eine Klasseninstanz einen Zeiger auf sich selbst in einen vector, der ihre eigene Member-Variable ist, drückt, find ich schon seltsam, oder deleted die Klasse die Objekte hinter den zeigern in ihrem Member-Vektor nicht. Also wenn sie sie über die Zeiger nicht besitzt sondern nur kennt, geht das. Allerdings musst du dann halt immer aufpassen, dass die Klasse nicht mehr auf die Zieger in ihrem Vektor zugreift wenn die Objekte dahinter schon zerstört wurden.
    Falls die Klasse die Listener in ihrem Vector besitzt, könntest du es auch hier mit RAII probieren und dann shared_ptr benutzen:

    std::vector<std::shared_ptr<R7GridBufferListener>> m_listeners;
    

    Dann kann die Instanz sich aber natürlich nicht mehr selbst reinpushen. Ist das nötig, dass sie das kann? Ich kann mir nämlich auf Anhieb kein Szenario vorstellen, wo man das braucht.
    Vielleicht kannst du das Problem ja auf ein kompilierbares Minimalbeispiel reduzieren, sodass ich es bei mir hier auch scheppern lassen kann. 🙂
    (Wenn du irgendwelchen Kram aus einer Bibliothek dadrin hast, kannst du den ja durch Mock-Objekte ersetzen)



  • Dass eine Klasseninstanz einen Zeiger auf sich selbst in einen vector, der ihre eigene Member-Variable ist, drückt, find ich schon seltsam, oder deleted die Klasse die Objekte hinter den zeigern in ihrem Member-Vektor nicht.

    Haha, cooler Satz. Nein, ich habe dieses Stückchen Code nur benutzt, um zu zeigen, wie ich die pointer da reinpacke.
    In Wirklichkeit habe ich natürlich noch eine weitere Klasse, die m_listeners als member hat und bei der man sich über einen Methodenaufruf in m_listeners eintragen lassen kann. Da steht dann anstelle des "this" der als Parameter übergebene Pointer des aufrufenden. (oh je, es wird immer undurchsichtiger...)

    Ok, ich mache mal ein Minimalbeispiel draus und melde mich dann nochmal.



  • tja, was soll ich sagen: Danke für die Hilfe zur Selbsthilfe!

    Ich habe meine Klassen in eine neues Programm gepackt und da ging alles. Tja, komisch.
    Vorhin kam mir plötzlich die Idee: Wenn meine Instanzen zerstört werden, verbleiben Ihre pointer als Listener im Speicher. Ein Zugriff darauf geht dann natürlich ins Nirvana.
    Der Host, der mein Plugin läd, baut irgendwie die Benutzeroberfläche auf, zerstört sie sofort wieder und baut sie dann erneut auf um sie anzuzeigen. (Warum? Vielleicht will der Host erst Prüfen, ob das Plugin ordnungsgemäß funktioniert?! Keine Ahnung).
    Jedenfalls wurde dadurch der pointer auf ein Objekt zweimal eingefügt, das erste Mal von der instanz, die der Plugin Host wieder abgebaut hat. Das zweite Mal dann von der Instanz, die es letztlich auch auf den Monitor schafft. Tja, damit ist antürlich in der Liste ein baumelnder pointer drin.
    Ich habe jetzt ein kleines Management hinzugefügt, was sie auch um ein "Austragen" aus der Liste kümmert, wenn das Objekt zerstört wird.

    Danke jedenfalls für die umfangreiche Hilfe, sowas ist Gold wert!!
    StrangeMan


Anmelden zum Antworten