Mal wieder ein "pure virtual function call"
-
Welchen Sinn hat es, die Methode in der Basisklasse public aber in den Kindklassen private zu machen?
-
Aha, der Herr ist also ein Drei-Sterne-Programmierer. Und heute kocht er Spaghetti.
Ich sag mal so: Ich kann zwar nicht mit dem Finger auf die genaue Problemstelle zeigen, aber das muss ich gar nicht. Der Code hat sehr viele Probleme, dein Fehler ist bloß das Symptom. Typischerweise bekommt man deinen Fehler nämlich bei ungültigen Pointeroperationen oder wenn man wilde Sachen im Konstruktor/Destruktor macht, die virtuelle Funktionen aufrufen. Letzteres kann ich nicht beurteilen, weil du die entscheidenden Konstruktoren und Destruktoren nicht zeigst. Aber ungültige Pointer: Oh ja! Die hast du garantiert.
Außerdem ist dein Basisklassendestruktor nicht virtual, was auch dicke Probleme geben wird, wenn du die Klasse Polymorph einsetzt. Und die Regel der großen Drei verletzt du auch fröhlich. Vielleicht sind dies schon deine fehlerverursachendes Problem.Tipp: Wenn du so programmierst, weißt du vermutlich gar nichts über grundlegende C++-Designprinzipien wie RAII, die Regel der großen Drei, Smartpointer, STL-Container. Das solltest du alles dringendst nachholen. Und dann nochmal sauber neu programmieren, dann können solche Fehler gar nicht erst entstehen. Das ist mein ernsthafter Ratschlag, den du wahrscheinlich nicht befolgen wirst und stattdessen weitere Stunden nach dem Fehler suchst. Und beim nächsten Programm wieder. Und dann wieder. Und wieder.
Übrigens: Wenn du im Forum [cpp]-Tags anstatt [code] benutzt, dann wird dein Code viel besser lesbar, wegen Syntaxhervorhebung.
-
Hallo und danke für die Antworten.
Die Konstruktioren/Destoruktoren machen eigentlich nur die Initialisierung von Member-Variablen.
den du wahrscheinlich nicht befolgen wirst und stattdessen weitere Stunden nach dem Fehler suchst
Nein, weitere Stunden werde ich nicht suchen. Ich hab das ja nicht so programmiert, weil ich dich ärgern will, sondern, weil ich es nicht besser wusste. Riesige mehrdimensionale Arrays aus Pointern zu verwalten fand ich von vorn herein sehr gewöhnungsbedürftig (und es schien stets eine recht "instabile" Sache zu sein), aber mangels passender Alternativen habe ich diese Variante nehmen müssen und dabei versucht, keinen Quatsch zu machen.
Danke für deine Stichworte, ich werde mich mal dazu belesen. Insbesondere STL Container klingen spannend und könnten wunderbar für meinen Fall passen. mit <vector> habe ich schon herum experimentiert, aber ich muss dabei etwas falsch gemacht haben - denn der Compiler mochte meine Versuche nicht besonders.Habe hier im Forum ein kleines Tutorial gefunden, mit dessen Hilfe werde ich jetzt mal versuchen, meinen "code" umzustellen (besser jetzt als später).
Schritt 1: Die "Listener"-Sache anders lösen.
Und da habe ich schon die erste Frage: Wie verwalte ich die Pointer-Liste in GridBuffer? Ich dachte jetzt an "list", da ich die Elemente sowieso nur der Reihe nach durchgehe um sie aufzurufen. Mal eben einen Zugriff auf Element xy brauche ich nicht. Ist das eine passende Wahl?
Ich habe also jetzt erstmal ganz dumm versucht, mein Array aus Zeigern durch eine Liste zu ersetzen:list<*R7GridBufferListener> m_listeners;Da die list ja scheinbar für alle beliebigen Datentypen geht, sollte das so doch klappen (dachte ich mir). Aber da fängts schon an "Ungültige Verwendung dieses Typs als Ausdruck".
Vielleicht könntet ihr mir auf die Sprünge helfen, wie man sowas angeht. Wahrscheinlich stimmt schon der Ansatz nicht. Vielleicht sollte ich besser Fragen: Wie kann ich in meiner Klasse GridBuffer eine Liste von Listenern verwalten? Ich versuche inzwischen die restlichen Stichworte zu verstehen, die du mir genannt hast.
Danke für die Mühe,
StrangeMan
-
list ist fast immer eine schlechte Wahl. Die brauchst du nur, wenn du massiv Listen in der Mitte trennst oder zusammenführst. Grundcontainer sollte immer vector sein, der ist beim Zugriff nämlich mit Abstand der schnellste. Alles andere brauchst du eher, wenn einfache Zugriffe nicht den Großteil der Nutzung ausmachen.
Was sollen denn die ganzen Pointer? Ich hoffe die zeigen nur auf Objekte in anderen besitzenden Containern, besitzen aber die Objekte nicht selber. ansonsten schau mal die verschiedenen Smartpointer an. Im Programm direkt sichtbare Pointer braucht hat man in C++ eher selten, Ausnahme sind halt polymorphe Sachen, wie du sie hier hast, daher will ich dich nicht zu stark bedrängen.
Zu deinen Syntaxfehler: Da wirst du wohl die Syntax falsch gemacht haben
. Ohne Code kann man nix genaues sagen.
-
Der Stern gehört rechts vom Typ hin.
-
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 ©); 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 ©) { 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
-
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,
StrangeManPS: 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 FunktionMacher::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 PointerR7Grudbuffer* ptrmal 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!
StrangeManEdit: 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