Zugriff von Instanzvariablen über std::bind?



  • Entschuldigung für den offenbar nicht richtig dargestellten Sachverhalt. Im Nachhinein hätte ich als Überschrift besser "Wie greife ich auf eine Instanzvariable aus einer anderen Klasse zu?" schreiben sollen.

    @hustbaer sagte in Zugriff von Instanzvariablen über std::bind?:

    • Wo in deinem Code engineL = new Engine(); und engineR = new Engine(); stehen

    Der Code steht im Konstruktor von MainWindow.cpp.

    • Die Definition der Klasse Engine

    Mir ist schon klar, dass ich eine Funktion brauche, um die Variablen zu ändern und ggf. eine andere zum Auslesen.
    Code Engine.h

    class Engine
    {
    public:
    	Engine();
    	int GetN1(Engine eng);
    private:
    	int N1;
    	int N2;
    	int N1_soll;
    	bool runswithoutAPU;
    };
    
    • Wo der Zugriff auf die Werte der Triebwerke erfolgen soll

    Zum Lesen aus einer bisher nicht genannten Klasse. Welche Klasse das konkret ist bzw. wie sie heißt, weiß ich noch nicht.

    Ich verstehe nichtmal was du damit erreichen willst. Was soll Engine::GetLN1 sein und wie sollte man es deiner Meinung nach verwenden können?
    Was ich dir aber mit an Sicherheit grenzender Wahrscheinlichkeit sagen kann, ist dass &engineL hier keinen Sinn macht. In deinem Beispiel ist engineL anscheinend ein Zeiger (engineL = new Engine(); und so). D.h. &engineL wäre ein Zeiger >auf diesen Zeiger. Etwas mehr Sinn machen würde engineL. Wobei das vermutlich auch nicht funktionieren wird. Ist halt >ein Ratespiel, du verrätst ja nicht was Engine::GetLN1 ist/soll.

    Engine::GetLN1 soll den Wert der Instanzvariablen engineL.N1 bereitstellen
    Dass man das mit std::bind lösen könnte und ich die Idee hatte, es mit

    Engine::GetLN1 = std::bind(&Engine::GetN1, &engineL, _1)(Engine);
    

    zu versuchen, basiert auf meinen Beitrag mit der Antwort von Th69

    Berechne::OnUpdate = std::bind(&MainWindow::UpdateMonitors, this);
    

    und im Internet mit dem Beispiel

    bind(&X::f, &x, _1)(i);
    

    Meines Wissens nach ist es doch so, dass niemand außer MainWindow.cpp die Instanzen engineL und engineR kennt. Deshalb muss ich doch diese irgendwie als Parameter übergeben um letztendlich den aktuellen Wert abrufen zu können. Ich dachte, dass die Variable "Engine::GetLN1" über die Funktion Engine::GetN1 und den Parameter engineL den Wert von englineL.N1 ausgibt.



  • mhm, wenn Du in main eine

    int i;
    

    definierst, und willst den Wert in einer aufgerufenen Funktion wissen, musst Du die Funktion halt mit i als Argument aufrufen ...

    void funktion (int p)
    {
       cout << "Wert von i in main ist: " << p;
    }
    
    int main()
    {
       int i {12};
    
       funktion (i);
    
    }
    

    Und so musst Du dann auch Deine Engines weiterreichen oder sonstwie bekannt machen (ganz schlechte Variante wäre globale Definition) ...



  • @stefanpc81. So wie @hustbaer schon geschrieben hat, macht es doch keinen Sinn eine Memberfunktion zu definieren, welche ein anderes Objekt als Parameter hat um darauf dann zuzugreifen (und nicht auf die eigenen Member).

    Da fehlt wohl noch etwas vom objektorientierten Verständnis?!

    Und wenn du die Werte der beiden Engine-Instanzen aus einer anderen Klasse abrufen möchtest, dann übergebe diese an diese Klasse (bzw. ein konkretes Objekt davon) als Zeiger bzw. Referenz, z.B.:

    engineL = new Engine();
    engineR = new Engine();
    
    OtherClass otherClass(engineL, engineR); // im Konstruktor diese beiden Variablen an zugehörige Member kopieren
    
    otherClass.DoSomethingWithEngines(); // intern wird dann auf die Member zugegriffen
    

    Mit deinem bind bist du jedenfalls auf dem Holzweg, da du ja jetzt konkrete Objekte (Instanzen) hast und keine Funktionen.



  • @Th69 sagte in Zugriff von Instanzvariablen über std::bind?:

    @stefanpc81. So wie @hustbaer schon geschrieben hat, macht es doch keinen Sinn eine Memberfunktion zu definieren, welche ein anderes Objekt als Parameter hat um darauf dann zuzugreifen (und nicht auf die eigenen Member).

    Da fehlt wohl noch etwas vom objektorientierten Verständnis?!

    Wahrscheinlich habe ich zu kompliziert gedacht und parallel zu viel herumprobiert...

    Und wenn du die Werte der beiden Engine-Instanzen aus einer anderen Klasse abrufen möchtest, dann übergebe diese an diese Klasse (bzw. ein konkretes Objekt davon) als Zeiger bzw. Referenz, z.B.:

    engineL = new Engine();
    engineR = new Engine();
    
    OtherClass otherClass(engineL, engineR); // im Konstruktor diese beiden Variablen an zugehörige Member kopieren
    
    otherClass.DoSomethingWithEngines(); // intern wird dann auf die Member zugegriffen
    

    Danke soweit. Mir ist jetzt klar geworden, dass meine "bisher nicht genannte Klasse" PaintEICAS ist (QWidget).
    PaintEICAS.h

    class PaintEICAS : public QWidget
    {
    	Q_OBJECT
    
    public:
    	PaintEICAS(QWidget* parent);
    	~PaintEICAS();
    	void paintEvent(QPaintEvent* event);
    //...
    }
    

    PaintEICAS.cpp

    PaintEICAS::PaintEICAS(QWidget* parent) : QWidget(parent)
    {}
    
    PaintEICAS::~PaintEICAS()
    {}
    
    void PaintEICAS::paintEvent(QPaintEvent* event)
    {
    	setUpdatesEnabled(false);
    
    	QPainter painter;
    	painter.begin(this);
    //...
    	//Engine Indication
    	int startAngle1 = 0;
    	int spanAngle1 = -180 * 16;
    	painter.setPen(MyColor::white);
    	painter.setBrush(MyColor::black);
    	painter.drawArc(15, 60, 70, 70, startAngle1, spanAngle1);
    	painter.drawRect(50,73,40,20);
    	painter.setBrush(MyColor::grayEICAS);
    	int startAngle2 = 0;
    	int spanAngle2 = -70 * 16; //hier soll spanAngle2 den Wert von engineL.N1 * 16 haben
    	//also etwa so: int spanAngle2 = engineL.GetN1() * 16;
    	painter.drawPie(15,60,70,70,startAngle2,spanAngle2);
    //...
    	painter.end();
    	setUpdatesEnabled(true);
    }
    

    Wie im Code der cpp-Datei zu sehen ist, möchte ich den Wert engineL.N1 hier im paintEvent bei spanAngle2 zur Verfügung haben. Gelesen habe ich im Internet, dass man die paintEvent-Funktion leider nicht überladen kann. Die beiden Engine-Member mit dem Konstruktor von PaintEICAS() zu überladen, macht also hier meiner Meinung nach keinen Sinn, da die Member in der paintEvent-Funktion dann auch nicht bekannt sind.
    In meiner Klasse Engine habe ich den Code für GetN1() mal so definiert (public):

    int Engine::GetN1()
    {
    	return this->N1;
    }
    

    Wie kann man das jetzt lösen? Sorry für die früheren ungenauen Posts meinerseits.
    Zur Info: Das paintEvent wird über einen QTimer alle 500ms über update neu gezeichnet, was auch funktioniert.



  • OK. Also das Problem ist dass du in PaintEICAS den N1 Wert der Engine brauchst, damit das PaintEICAS Widget diesen anzeigen kann. Hab ich das soweit richtig verstanden?

    Eine Möglichkeit wäre die beiden Klassen völlig entkoppelt zu lassen und den Wert von aussen in das PaintEICAS zu setzen. D.h. du machst einfach eine PaintEICAS::SetN1 Funktion. PaintEICAS speichert den Wert dann einfach in einer Membervariable, und dann hat es im Paint Event Handler natürlich Zugriff auf diesen Wert.

    Dazu musst du nur wissen wann sich der N1 Wert einer Engine ändern kann, und dann jedes mal PaintEICAS::SetN1 aufrufen um den Wert der angezeigt wird zu ändern.

    Eine andere Möglichkeit wäre PaintEICAS einen Funktor zu übergeben mit dem der N1 Wert ausgelesen werden kann, wann immer dieser benötigt wird. Da wären wir dann beim std::bind bzw. Lambdas.

    Und die dritte Möglichkeit wäre PaintEICAS einen Zeiger auf das Engine Objekt zu übergeben. Das würde allerdings eine starke Kopplung zwischen PaintEICAS und Engine erzeugen. Wenn PaintEICAS sowieso nur in Kombination mit Engine Sinn machen kann, dann wäre das wohl OK. Ansonsten würde ich eher versuchen das zu vermeiden.

    Das Übergeben des Funktors bzw. des Zeigers kannst du z.B. einfach im Konstruktor machen. Oder, falls das aus irgend einem Grund nicht geht, kannst du natürlich auch einen Setter machen mit dem der Funktor bzw. Zeiger nachträglich gesetzt werden kann.

    Möglichkeit 2 und 3 machen aber IMO nur Sinn, wenn die ganze Anzeige periodisch komplett neu gezeichnet wird. Das ist z.B. üblich bei Programmen die eine 3D API zur Anzeige verwenden. Bei Programmen die die klassischen Window-Manager APIs zum Anzeigen verwenden ist es aber nicht üblich. Da zeichnet man eher nur die Controls neu wo sich etwas geändert hat. D.h. du bräuchtest bei Möglichkeit 2 und 3 sowieso noch einen Weg herauszufinden wann sich etwas geändert hat, damit du dann das Neuzeichnen des Controls auslösen kannst. Und an der Stelle kannst du mMn. dann auch gleich die PaintEICAS::SetN1 Funktion von Möglichkeit 1 aufrufen.



  • @stefanpc81 sagte in Zugriff von Instanzvariablen über std::bind?:

    Wie im Code der cpp-Datei zu sehen ist, möchte ich den Wert engineL.N1 hier im paintEvent bei spanAngle2 zur Verfügung haben. Gelesen habe ich im Internet, dass man die paintEvent-Funktion leider nicht überladen kann. Die beiden Engine-Member mit dem Konstruktor von PaintEICAS() zu überladen, macht also hier meiner Meinung nach keinen Sinn, da die Member in der paintEvent-Funktion dann auch nicht bekannt sind.

    Das macht überhaupt keinen Sinn. Dir fehlt leider noch sehr viel an Grundlagen.



  • Hallo @stefanpc81,
    auch mit dem Risiko, Dich zu verwirren; aber in Qt gibt es auch noch das Signal-Slot-Konzept (sie z.B. hier https://de.wikipedia.org/wiki/Signal-Slot-Konzept).



  • @Helmut-Jakoby

    auch mit dem Risiko, Dich zu verwirren; aber in Qt gibt es auch noch das Signal-Slot-Konzept (sie z.B. hier https://de.wikipedia.org/wiki/Signal-Slot-Konzept).

    Danke für den Hinweis, ist mir allerdings schon bekannt.

    @hustbaer

    OK. Also das Problem ist dass du in PaintEICAS den N1 Wert der Engine brauchst, damit das PaintEICAS Widget diesen anzeigen kann. Hab ich das soweit richtig verstanden?

    Ja, genau. Ich möchte allerdings auch, dass die Funktion(en) beide Instanzen verarbeiten können, d.h. ich brauche die Werte jeweils von engineL und engineR.

    Eine Möglichkeit wäre die beiden Klassen völlig entkoppelt zu lassen und den Wert von aussen in das PaintEICAS zu setzen. D.h. du machst einfach eine PaintEICAS::SetN1 Funktion. PaintEICAS speichert den Wert dann einfach in einer Membervariable, und dann hat es im Paint Event Handler natürlich Zugriff auf diesen Wert.

    Diese Variante würde ich bevorzugen. Die beiden Instanzen in den Konstruktor von PaintEICAS zu übergeben, war recht einfach. Sorry, wie ihr merkt, bin ich noch Anfänger. Ich weiß nämlich nicht, wie die Funktion "PaintEICAS::SetN1" die Instanz bzw. den -Member, also bspw. engineL, herbekommt. Nachfolgend noch mein überarbeiteter Code.

    Dazu musst du nur wissen wann sich der N1 Wert einer Engine ändern kann, und dann jedes mal PaintEICAS::SetN1 aufrufen um den Wert der angezeigt wird zu ändern.

    Verstehe ich.

    Engine.cpp

    Engine::Engine()
    {
    	SetN1(0);
    	N2 = 0;
    	N1_soll = 0;
    	N2_soll = 0;
    	runswithoutAPU = false;
    }
    
    int Engine::GetN1() const
    {
    	return N1;
    }
    
    void Engine::SetN1(int n)
    {
    	N1 = n;
    }
    
    

    Und Engine.h

    class Engine
    {
    public:
    	Engine();
    public:
    	int GetN1() const;
    	void SetN1(int n);
    private:
    	int N1;
    	int N2;
    	int N1_soll;
    	int N2_soll;
    	bool runswithoutAPU;
    	int n;
    };
    

    PaintEICAS.cpp-Konstruktor

    PaintEICAS::PaintEICAS(Engine* engineL, Engine* engineR, QWidget* parent) : QWidget(parent)
    {
    }
    


  • Genua so meinte ich es mit dem Konstruktor. Jetzt fehlen nur noch passende Member in der Klasse (was ich mit meinem Kommentar im letzten Code meinte):

    class PaintEICAS : public QWidget
    {
      // ...
    
    private:
      const Engine *engineL;
      const Engine *engineR;
    }
    

    (const wenn du nur lesend darauf zugreifst - was für Viewklassen am sinnvollsten ist!)

    Und diese initialisierst du im Konstruktor:

    PaintEICAS::PaintEICAS(QWidget* parent, const Engine* engineL, const Engine* engineR) : QWidget(parent), engineL(engineL), engineR(engineR)
    {
    }
    

    (den parent würde ich immer als erstes übergeben, so daß du eine einheitliche Schnittstelle für alle deine Qt-Klassen hast)
    Falls dich stört (bzw. unleserlich/unverständlich ist), daß sowohl Konstruktorparameter als auch Member gleich heißen, so kannst du selbstverständlich auch dies ändern).

    Und dann greifst du einfach auf die Membervariablen z.B. in der paintEvent-Funktion zu:

    int spanAngle2 = engineL->GetN1() * 16;
    

    (also paßt jetzt -fast- dein vorheriger Kommentar ;- )

    Wenn du mehrere Qt-Klassen (Views) hast, die diese Engine-Daten benötigen, so solltest du besser dafür eine eigene Struktur/Klasse anlegen, welche du dann als ein Parameter übergibst - gerade wenn evtl. auch noch andere Daten benötigt werden.

    Außerdem könntest du dir dann überlegen, ob du nicht sogar MVVM einsetzen möchtest: Model/View Programming | Qt Widgets 6.2.1.

    PS: Membernamen wie n (in Engine) solltest du vermeiden, da sie nicht sehr aussagekräftig sind (und bei Benutzung im Code mit lokalen Variablen verwechselt werden könnten).
    Ich nehme mal an, daß (zumindestens) für dich N1, N2 sinnvolle Namen sind, ansonsten gilt gleiches auch hier.



  • Vielen Dank an alle, insbesondere hat Th69 sehr ausführlich geantwortet, danke schön!

    @Th69

    (den parent würde ich immer als erstes übergeben, so daß du eine einheitliche Schnittstelle für alle deine Qt-Klassen hast)

    Da hat der Compiler gemeckert -> der QWidget* parent solle nach hinten, warum auch immer...

    Falls dich stört (bzw. unleserlich/unverständlich ist), daß sowohl Konstruktorparameter als auch Member gleich heißen, so kannst du selbstverständlich auch dies ändern).

    Danke, weiß ich



  • @stefanpc81
    Was ich meinte wäre so:

    class PaintEICAS : public QWidget
    {
    public:
        // ...
        void SetN1Values(int left, right) {
            engineL_N1 = left;
            engineR_N1 = right;
        }
    
    private:
        // ...
        int engineL_N1 = 0;
        int engineR_N1 = 0;
    };
    

    SetN1Values muss dann immer aufgerufen werden wenn sich einer der N1 Werte geändert haben könnte. Falls das in der Klasse passiert die auch die engineL und engineR Member hat, kannst du das einfach so aufrufen:

        otherClass.SetN1Values(engineL->GetN1(), engineR->GetN1());
    

    Und in PaintEICAS::paintEvent verwendest du dann einfach die Werte engineL_N1 und engineR_N1.



  • @hustbaer
    Danke für den Vorschlag, aber später kommen noch die anderen Werte dran:
    engineL/R->
    N2,
    N1_soll,
    N2_soll
    Die sollen aber nicht alle über die gleiche Klasse aufgerufen werden. Da ist es mit meiner Aufteilung in zwei Engine-Instanzen einfacher (mMn).



  • @stefanpc81 sagte in Zugriff von Instanzvariablen über std::bind?:

    @Th69

    (den parent würde ich immer als erstes übergeben, so daß du eine einheitliche Schnittstelle für alle deine Qt-Klassen hast)

    Da hat der Compiler gemeckert -> der QWidget* parent solle nach hinten, warum auch immer...

    Da würde mich die genaue Fehlermeldung interessieren.
    Wahrscheinlich wegen der Angabe des Standardwerts = 0 (dieses darf nicht alleinig bei den ersten Parametern stehen - wie soll man dann die ersten Parameter auslassen?). Aber den kann man ja auch weglassen und jeweils explizit beim Aufruf setzen.



  • @stefanpc81 sagte in Zugriff von Instanzvariablen über std::bind?:

    Die sollen aber nicht alle über die gleiche Klasse aufgerufen werden.

    Ich verstehe nicht was du damit meinst.
    Was soll "einen Wert aufrufen" bedeuten? Und was soll "über eine Klasse aufrufen" bedeuten?



  • @hustbaer

    Ich verstehe nicht was du damit meinst.
    Was soll "einen Wert aufrufen" bedeuten? Und was soll "über eine Klasse aufrufen" bedeuten?

    Was wir hier für "N1" erarbeitet haben, sollte für die Klasse PaintEICAS verfügbar sein. "N2" soll über die noch nicht vorhandene Klasse "PaintMFD" verfügbar sein. Ich meinte eigentlich "in einer Klasse aufrufen". Mit "Wert aufrufen" meinte ich die "Membervariable auslesen". Da habe ich mich falsch ausgedrückt.

    @Th69
    Wegen der Fehlermeldung: Das konnte ich für dich leider nicht mehr rekonstruieren. Ich weiß nicht mehr, wie zu diesem Zeitpunkt der Code aussah.



  • @Th69
    Ich habe gerade "deinen Fehler", den du wissen wolltest, gefunden! In der Headerdatei stand

    class PaintOHP : public QWidget
    {
    	Q_OBJECT
    
    public:
    	PaintOHP(QWidget* parent = Q_NULLPTR, const Engine* engineL, const Engine* engineR);
    //...
    }
    

    was den Fehler
    E0306 Das Standardargument befindet sich nicht am Ende der Parameterliste.
    ausgelöst hat. Ich hatte fälschlicherweise

    QWidget* parent = Q_NULLPTR
    

    hier eingetragen und nach dem "Verschieben" dieser Initialisierung in den Konstruktor der CPP-Datei ist wieder alles gut.
    Edit: War doch nicht alles gut. "Der 2. und 3. Standardparameter ist nicht definiert" hieß es. Erst nachdem ich

     = Q_NULLPTR
    

    ganz rausgenommen habe, funktioniert alles.



  • Ja, genau das meinte ich.
    Ich halte die Angabe des Standardwertes hier auch für überflüssig, da man ein Widget ja immer auf ein Window oder eine andere von Widget abgeleitete Klasse platziert und demnach explizit übergeben muß.

    Nur wenn es auch als eigenständiges Fenster angedacht ist, macht das m.E. Sinn - dann würde ich die Klasse aber auch explizit von QWindow ableiten (mit den entsprechenden Konstruktorparametern).

    Wie auch immer, ich hoffe, daß du jetzt mit deinem Projekt weiterkommst?!



  • @Th69

    Wie auch immer, ich hoffe, daß du jetzt mit deinem Projekt weiterkommst?!

    Danke, das ist eine sehr freundliche Nachfrage von dir! Die Antworten haben mir sehr geholfen und mal abwarten, wann ich ein neues Problem habe und mich mit einem neuen Beitrag melde 😉



  • Es geht (leider) schon weiter: Ich habe in der Qwidget-Klasse "PaintLowerControls" vor, die Werte von "Engine* engineL" zum Ändern zu bringen. Ich habe das const entfernt und mein Vorhaben scheitert offenbar daran, dass ich nicht mit den Zeigern und Referenzen ( * und &) klar komme bzw. mir noch das Verständnis dazu fehlt. Irgenwie muss ich hier ein & verwenden, aber ich verstehe nicht wie bzw. wo…

    PaintLowerControls.cpp

    PaintLowerControls::PaintLowerControls(QWidget* parent, Engine* engineL, Engine* engineR) :
    	QWidget(parent),
    	engineL(engineL),
    	engineR(engineR)
    {
    	QPushButton* leverLFuel = new QPushButton(this);
    	leverLFuel->setGeometry(QRect(65, 230, 20, 30));
    	leverLFuel->setStyleSheet("background-color: transparent;" "border: 0px");
    	connect(leverLFuel, SIGNAL(clicked()), this, SLOT(ClickFuelLeverL(engineL)));
    }
    
    void PaintLowerControls::ClickFuelLeverL(Engine* engineL)
    {
    	if (engineL->Get_lever_fuel() == "cutoff" && Current::GetLmain()) //soll alten Wert auslesen
    	{
    		//folgendes soll für den ursprünglichen Instanzmember engineL geändert werden
    		engineL->Set_lever_fuel("run");
    		engineL->Set_fuel_valve(true);
    	}
    //…
    }
    

    PaintLowerControls.h

    class PaintLowerControls : public QWidget
    {
    	Q_OBJECT
    
    public:
    	PaintLowerControls(QWidget* parent, Engine* engineL, Engine* engineR);
    	~PaintLowerControls();
    	void paintEvent(QPaintEvent* event);
    private slots:
    	void ClickFuelLeverL(Engine* engineL);
    private:
    	Engine* engineL;
    	Engine* engineR;
    };
    

    Engine.cpp

    std::string Engine::Get_lever_fuel() const
    {
    	return lever_fuel;
    }
    void Engine::Set_lever_fuel(std::string s)
    {
    	lever_fuel = s;
    }
    

    Engine.h

    //…
    public:
    	std::string Get_lever_fuel() const;
    	void Set_lever_fuel(std::string s);
    //…
    

    Current::GetLmain() gibt nebenbei bemerkt ein bool zurück. Ich wäre euch dankbar, wenn auch dieses Problem hier über das Forum gelöst werden könnte.



  • Was ist denn das genaue Problem? Kompiliert es nicht?

    Was ich sehe ist, daß du bei ClickFuelLeverL(...) engineL als Parameter übergibst, jedoch auch ohne diesen direkt auf die Membervariable zugreifen könntest.
    Beim SLOT mußt du die Signatur angeben und die Parameter getrennt bzw. einen Lambda-Ausdruck benutzen, s. z.B. Passing an argument to a slot.

    Wenn du ClickFuelLeverL immer mit derselben Engine benutzt (d.h. nur engineL und nicht auch mal engineR), dann kannst du den Parameter einfach weglassen:

    connect(leverLFuel, SIGNAL(clicked()), this, SLOT(ClickFuelLeverL()));
    

    sowie

    void PaintLowerControls::ClickFuelLeverL()
    {
      // ...
    }
    

Anmelden zum Antworten