Erklärung der Funktion std::bind und der std::placeholder

Schnelle objektorientierte, kompilierende Programmiersprache.
Antworten
PietCN
Beiträge: 13
Registriert: Sa Jan 16, 2016 10:49 pm

Erklärung der Funktion std::bind und der std::placeholder

Beitrag von PietCN » Fr Okt 28, 2016 6:22 pm

Hallo,
in meinem "Selbststudium" bin ich jetzt schön öfters über die Funktion std::bind gestoßen. So richtig kann ich mir noch nicht erklären wie genau diese funktioniert und selbst mit mehreren Erklärungen, welche ich im Netz gefunden habe, konnte ich noch nicht so viel damit anfangen, bzw. war mir dies dann nicht so richtig einleuchtend.

In meinem Projekt geht es darum, dass es ein Objekt (in meinem Fall ein Flugzeug) ein anderes Objekt (hier jetzt ein PickUp, welche das Leben wieder auffülen soll) einsammeln soll.
So habe ich die Klasse Aircraft für mein Flugzeug und ein Struct Pickup:

Code: Alles auswählen

class Aircraft {
...
  void repair(int points) { mHitpoints =+ points;}
  int   mHitpoints;
...
};

struct PickupData {
  Textures::ID                              textures;
  std::functional<void(Aircraft&)  action;
}
Hier wäre der erste Punkt ob ich es bisher richtig verstanden habe: std::function ist quasi ein Funktions-Wrapper in dem ich Funktionen mit der entsprechenden Signatur "hineinpacken" kann.
Das war soweit auch alles verständlich.

Nun wird das ganze initialisiert: da es mehrere Arten von Pickups gibt, hat jede Art seine eigene "Wirkungsweise" -> Funktion.

Code: Alles auswählen

std::vector<PickupData> data() {
  std::vector<PickupData> data(Pickup::TypeCount);   //Pickup ist die Klasse an sich der Pickups und TypeCount  das letzte Enum-Element in dem sämtliche Arten der Pickups drin stehen

  data[Pickup::HealtRefill].texture = Texture::HealtRefill;                                                     //TextureID übergeben welche in einem Enum Texture drin steht
  data[Pickup::HealtRefill].action = std::bind(&Aircraft::repair, std::placeholder::_1, 25);      //und hier ist nun die krux... es funktioniert aber warum ^^ 
...
  return data;
}
So richtig ist mir nicht ganz klar was nun wirklich hier geschieht. In den Erklärungen stand das std::bind Argumente an Funktionen bindet... aber was heißt das nun genau und was genau macht der placeholder an der Stelle.

Noch um ein anderes Pickup zu zeigen bei dem kein Argument erwartet wird

Code: Alles auswählen

...
  data[Pickup::FireRate].action = std::bind(&Aircraft::increasFireRate, std::placeholder::_1);
...
Vielleicht gibt es ja eine Erklärung die man als Normalsterblicher auch versteht. Was genau geschieht mit dem Placeholder und wie genau kann man sich das vorstellen das ein weiteres Argument an eine Funktion "gebunden" wird.

Mit freundlichen Grüßen

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von nufan » Sa Okt 29, 2016 12:52 am

Hallo! :)
Ich werde mal versuchen etwas Licht in die Sache zu bringen.
PietCN hat geschrieben:Hier wäre der erste Punkt ob ich es bisher richtig verstanden habe: std::function ist quasi ein Funktions-Wrapper in dem ich Funktionen mit der entsprechenden Signatur "hineinpacken" kann.
Ja, das stimmt so.
PietCN hat geschrieben:Vielleicht gibt es ja eine Erklärung die man als Normalsterblicher auch versteht. Was genau geschieht mit dem Placeholder und wie genau kann man sich das vorstellen das ein weiteres Argument an eine Funktion "gebunden" wird.
Sehen wir uns ein etwas einfacheres Beispiel an. Ich habe mir eine Funktion geschrieben, die mir die Summe aller Teiler bis zu einer Obergrenze zurück gibt.
Also z.B. der Teiler 3 bis zur Obergrenze 20:
3+6+9+12+15+18 = 63
Teiler 5 bis zur Obergrenze 20:
5+10+15+20 = 50

Die naive und sehr ineffiziente Implementierung sieht so aus:

Code: Alles auswählen

int sum(int div, int n)
{
    int s = 0;
    for(int i = 0; i <= n; i++)
    {
        if(i % div == 0)
            s += i;
    }
    return s;
}
Wir haben also 2 Parameter:
1) den Teiler div
2) die Obergrenze n

Unsere beiden Beispiele würden bei einem ganz normalen Aufruf so aussehen:

Code: Alles auswählen

std::cout << sum(3, 20) << std::endl;
std::cout << sum(5, 20) << std::endl;
Wir können nun optional Parameter unserer Funktion binden. Das funktioniert über die Funktion std::bind und verlangt lediglich die zu bindende Funktion (in diesem Fall sum) und alle Parameter der Funktion (in der normalen Reihenfolge).
Der einfachste Fall ist jener, in dem wir keinen Parameter binden. Wir müssen aber weiterhin Platzhalter für die Parameter angeben. Dabei steht std::placeholders::_1 für den ersten an die gebundene Funktion übergebenen Parameter, _2 für den zweiten Parameter usw. Geben wir die std::placeholders also in aufsteigender Reihenfolge an, ändert sich mal gar nix gegenüber dem direkten Funktionsaufruf von sum().

Code: Alles auswählen

auto sum_not_bound = std::bind(sum, std::placeholders::_1, std::placeholders::_2);
std::cout << sum_not_bound(3, 20) << std::endl;             // _1 = 3, _2 = 20 => sum(3, 20);
std::cout << sum_not_bound(5, 20) << std::endl;             // _1 = 5, _2 = 20 => sum(5, 20);
Wir können die Reihenfolge der Platzhalter aber auch beliebig ändern:

Code: Alles auswählen

auto sum_swapped_order = std::bind(sum, std::placeholders::_2, std::placeholders::_1);
std::cout << sum_swapped_order(20, 3) << std::endl;         // _1 = 20, _2 = 3 => sum(3, 20);
std::cout << sum_swapped_order(20, 5) << std::endl;         // _1 = 20, _2 = 5 => sum(5, 20);
std::bin ermöglicht es nun aber auch den Wert bestimmter Parameter zu fixieren. Anstatt eines Platzhalter wie std::placeholders::_1 verwenden wir also einen konkreten Wert. Im folgenden Beispiel legen wir den Wert des Parameters n auf 20 fest.

Code: Alles auswählen

auto sum_fixed_upper_bound = std::bind(sum, std::placeholders::_1, 20);
std::cout << sum_fixed_upper_bound(3) << std::endl;         // _1 = 3 => sum(3, 20);
std::cout << sum_fixed_upper_bound(5) << std::endl;         // _1 = 5 => sum(5, 20);
Wir können aber auch alle Parameter binden.

Code: Alles auswählen

auto sum_all_fixed_1 = std::bind(sum, 3, 20);
auto sum_all_fixed_2 = std::bind(sum, 5, 20);
std::cout << sum_all_fixed_1() << std::endl;                       // sum(3, 20);
std::cout << sum_all_fixed_2() << std::endl;                       // sum(5, 20);
Schlussendlich kann das Binden auch schrittweise - basierend auf einem vorherigen std::bind() Aufruf - passieren.

Code: Alles auswählen

auto sum_also_all_fixed_1 = std::bind(sum_fixed_upper_bound, 3);
auto sum_also_all_fixed_2 = std::bind(sum_fixed_upper_bound, 5);
std::cout << sum_also_all_fixed_1() << std::endl;               // sum(3, 20);
std::cout << sum_also_all_fixed_2() << std::endl;               // sum(5, 20);
Hier nochmal der gesamte Code falls du damit herumspielen willst:

Code: Alles auswählen

#include <iostream>
#include <functional>

int sum(int div, int n)
{
    int s = 0;
    for(int i = 0; i <= n; i++)
    {
        if(i % div == 0)
            s += i;
    }
    return s;
}

int main()
{
    std::cout << sum(3, 20) << std::endl;
    std::cout << sum(5, 20) << std::endl;
    std::cout << std::endl;

    auto sum_not_bound = std::bind(sum, std::placeholders::_1, std::placeholders::_2);
    std::cout << sum_not_bound(3, 20) << std::endl;
    std::cout << sum_not_bound(5, 20) << std::endl;
    std::cout << std::endl;

    auto sum_swapped_order = std::bind(sum, std::placeholders::_2, std::placeholders::_1);
    std::cout << sum_swapped_order(20, 3) << std::endl;
    std::cout << sum_swapped_order(20, 5) << std::endl;
    std::cout << std::endl;

    auto sum_fixed_upper_bound = std::bind(sum, std::placeholders::_1, 20);
    std::cout << sum_fixed_upper_bound(3) << std::endl;
    std::cout << sum_fixed_upper_bound(5) << std::endl;
    std::cout << std::endl;

    auto sum_all_fixed_1 = std::bind(sum, 3, 20);
    auto sum_all_fixed_2 = std::bind(sum, 5, 20);
    std::cout << sum_all_fixed_1() << std::endl;
    std::cout << sum_all_fixed_2() << std::endl;
    std::cout << std::endl;

    auto sum_also_all_fixed_1 = std::bind(sum_fixed_upper_bound, 3);
    auto sum_also_all_fixed_2 = std::bind(sum_fixed_upper_bound, 5);
    std::cout << sum_also_all_fixed_1() << std::endl;
    std::cout << sum_also_all_fixed_2() << std::endl;

    return 0;
}

Kommen wir nun auf deine Beispiele zurück.
PietCN hat geschrieben:

Code: Alles auswählen

void repair(int points) { mHitpoints =+ points;}
// ...
data[Pickup::HealtRefill].action = std::bind(&Aircraft::repair, std::placeholder::_1, 25);      //und hier ist nun die krux... es funktioniert aber warum ^^ 
Du erstellst dir hier einen Wrapper für deine Methode Aircraft::repair(), in dem der Parameter points fix auf 25 gesetzt ist. std::placeholder::_1 steht hier für den this-Zeiger, der implizit an jede Objekt-Methode übergeben wird.
PietCN hat geschrieben:

Code: Alles auswählen

data[Pickup::FireRate].action = std::bind(&Aircraft::increasFireRate, std::placeholder::_1);
Du bindest hier deine Methode Aircraft::increaseFireRate(), jedoch ohne Parameter fix zu setzen. Es wird lediglich der Placeholder für this gesetzt. Das hat auf den ersten Blick nicht viel Sinn, allerdings ist das praktisch wenn du dir deine PickupData-Struktur ansiehst:
PietCN hat geschrieben:

Code: Alles auswählen

struct PickupData {
  Textures::ID                              textures;
  std::functional<void(Aircraft&)  action;
}
Du kannst auf action also beliebige Funktionen setzen, die nur eine Referenz auf ein Aircraft-Objekt übergeben bekommen. Ich hätte an dieser Stelle ehrlich gesagt auf einen Zeiger gehofft, aber vielleicht kannst du uns ja einen Aufruf von so einer action zeigen :D

Konnte ich dir weiterhelfen?

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von nufan » Sa Okt 29, 2016 10:57 am

nufan hat geschrieben:Ich hätte an dieser Stelle ehrlich gesagt auf einen Zeiger gehofft, aber vielleicht kannst du uns ja einen Aufruf von so einer action zeigen :D
Achtung: https://stackoverflow.com/questions/16016112/

PietCN
Beiträge: 13
Registriert: Sa Jan 16, 2016 10:49 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von PietCN » Sa Okt 29, 2016 11:08 am

Also erstmal danke, durch dein vereinfachtes Beispiel ist mir doch schon alles etwas klarer geworden.
Was mir bei meinem Problem dabei nicht ganz so hilft ist, dass ich einen std::function habe in dem
Funktionen mit der Signatur - Rückgabe void und einem Parameter vom Typ Referenz auf Aircraft.

Aber ich dann in dem std::bind einen platzhalter rein haue und als zweiten Parameter ein int. Und wie genau dann dieser int-Wert der Funktion übergeben wird.

Dazu die Frage, wenn ich an eine Klassenmethode Argumente binden möchte, ist der erste Parameter immer der this-Zeiger?

Den Aufruf zu zeigen ist etwas kompliziert:
In meinem Projekt sind alle Objekte (hier Aircraft) von einer Klasse SceneNode abgeleitet.
Jeder SceneNode wird durch eine Category bestimmt (Category ist ein Enum in dem sämtliche Kategorien binär gespeichert sind).
Die ganze Datenstruktur sämtlicher existierenden Objekte ist
dann ein Binärbaum, in dem jeder Knoten(SceneNode) wieder mehrere Knoten beinhalten kann.

Das wie und warum ist jetzt zu aufwändig um das zu beschreiben.

Nun habe ich, um sämtliche Eingaben oder Aktionen (hier das einsammeln eines Objektes) zu Zentralisieren, ein konstrukt erstellt :

Code: Alles auswählen

struct Command {
  Category                           category;
  std::functional<void(SceneNode&)>  action;
}
Es gibt dann noch eine Templatefunktion mit der man einfach ein abgeleitetes Objekt von SceneNode in ein "echtes" SceneNode Klasse umwandeln kann.
Wie genau die Implementation davon ist, kann ich ja wenn es nötig ist noch mit angeben.

Code: Alles auswählen

class Commandqueue {
  ...
  Command pop();
  void push(Command command);
  ...
};
Ich habe dazu eine Tabelle erstellt in dem sämtliche Eigenschaften der ganzen Pickups drin stehen:
(wie es im ersten Thread ersichtlich ist)

Code: Alles auswählen

std::vector<PickupData> table = data() // Initialisierung im ersten Thread

...
collision()
{
  ...
  mCommandQueue.push(table[HealtRefill].action);  //in der Commandqueue wird dann meine Repair rein gepackt
                                                  //HealtRefill ist ein Element im Enum, welches in der Klasse Pickup existiert ( die Klasse Pickup ist widerum von SceneNode abgeleitet
}
In der Hauptapplication wird dann abgefragt ob denn Einträge in der Commandqueue existieren und wenn ja, für wem (für welchen SceneNode)

Code: Alles auswählen

...
  while (!mCommandQueue.empty()) {
    mSceneGraph.onCommand(mCommandQueue.pop());   //mSceneGraph ist der Wurzelknoten, von dem alle anderen Knoten ausgehen.
  }
...

Code: Alles auswählen

...
  //im aktiven SceneNode
  onCommand(const Command& command) {
    if (command.category == getCategory())   //abfrage ob der aktuelle SceneNode, der jetzt ausführende der Aktion ist
      command.action();
}
Da wird dann die entsprechende Funktion ausgeführt.

Ich hoffe ich hab jetzt soweit alles wichtige und wesentliches mit aufgeführt.


Nun die letzendliche Frage ist -> mein Funktionswrapper beinhaltet die Funktionssignatur void (SceneNode&), mit dem std::bind übergebe ich aber nun die Funktion repair mit dem Argument int.... bzw. halt bei anderen mit gar keinen Argumenten.

PietCN
Beiträge: 13
Registriert: Sa Jan 16, 2016 10:49 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von PietCN » Sa Okt 29, 2016 11:13 am

OK ich sehe schon die Umwandlung muss ich auch noch mit angeben^^:

Code: Alles auswählen

template <typename GameObject, typename Function>
std::function<void(SceneNode&)> derivedAction(Function fn)
{
	return [=](SceneNode& node)
	{
		//wandelt 
		assert(dynamic_cast<GameObject*>(&node) != nullptr);

		fn(static_cast<GameObject&>(node));
	};
}
Ich schaue halt erst nach, bist du überhaupt eine abgeleitete Klasse von meiner Main-Klasse und gebe dann die Funktion entsprechend zurück.
Wenn dies nicht der Fall ist, soll mir mein Compiler den Fehler anzeigen. Im Release kann man sowieso dann nichts mehr beim "Kunden" ändern, deswegen die Lösung so.

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von nufan » Sa Okt 29, 2016 1:43 pm

PietCN hat geschrieben:Aber ich dann in dem std::bind einen platzhalter rein haue und als zweiten Parameter ein int. Und wie genau dann dieser int-Wert der Funktion übergeben wird.
Den int-Wert übergibt dein Wrapper-Objekt für dich, das du von std::bind zurückbekommen hast.
PietCN hat geschrieben:Dazu die Frage, wenn ich an eine Klassenmethode Argumente binden möchte, ist der erste Parameter immer der this-Zeiger?
Ja, der ist immer da, du schreibst ihn nur nie explizit hin. In Python müsstest du das z.B. machen.
PietCN hat geschrieben:

Code: Alles auswählen

    ...
      //im aktiven SceneNode
      onCommand(const Command& command) {
        if (command.category == getCategory())   //abfrage ob der aktuelle SceneNode, der jetzt ausführende der Aktion ist
          command.action();
    }
Da wird dann die entsprechende Funktion ausgeführt.
Da wurde die SceneNode inzwischen aber noch gebunden, oder?
PietCN hat geschrieben:Nun die letzendliche Frage ist -> mein Funktionswrapper beinhaltet die Funktionssignatur void (SceneNode&), mit dem std::bind übergebe ich aber nun die Funktion repair mit dem Argument int.... bzw. halt bei anderen mit gar keinen Argumenten.
Hm ich weiß nicht ob du das richtige meinst, ich würd es genau anders herum formulieren. Du hast eine Funktion repair mit einem this-Zeiger und einem int-Parameter. Über das std::bind erstellst du eine neue Signatur, in der der int-Parameter nicht mehr vorhanden ist.

PietCN
Beiträge: 13
Registriert: Sa Jan 16, 2016 10:49 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von PietCN » Sa Okt 29, 2016 2:34 pm

nufan hat geschrieben:
PietCN hat geschrieben:

Code: Alles auswählen

    ...
      //im aktiven SceneNode
      onCommand(const Command& command) {
        if (command.category == getCategory())   //abfrage ob der aktuelle SceneNode, der jetzt ausführende der Aktion ist
          command.action();
    }
Da wird dann die entsprechende Funktion ausgeführt.
Da wurde die SceneNode inzwischen aber noch gebunden, oder?
Was meinst du genau damit ? Also diese ganze zusammenhängende Datenstruktur ist find ich sehr komplex und die Commandqueue schlängelt sich durch sehr viele Klassen um am Ende "zum Ziel zu kommen."

- Ein Command fängt das Ereignis auf(Tastatureingaben, Mausbewegungen, sonstiges Perepherieeingaben);

- Dieser Command geht dann zu einer Controler-Klasse und in dieser Controler-Klasse wird das Command in die Commandqueue gebracht; (Controler verarbeitet die gesamten Perepherie-Inputs, Interupts, etc.)

- Die Commandqueue wird dann in einem StateStack geschickt, in dem alle aktiven States abgeklappert werden; (Intro, Menüs, aktives Geschehen; Pause, Credits, etc.)

- In dem jeweiligem State wird dann die Commandqueue dann dem, ich nenn das mal den "StateControler" übergeben

- Hier an der Stelle werden dann der Queue State-Spezifische Commands hinzugefügt, wie in meinem Beispiel jetzt das einsammeln eines Objektes;

- Vom State-Controler geht die Queue dann zum Root-SceneNode in dem dann das über alle anderen verknüpften Nodes verteilt wird und jeder Node sich dann anschaut über die vergeben Category über die Message / Command für den jeweiligen Node gedacht ist.

Bei meiner GUI implementation hatte ich das auch schon gehabt, aber das ist mir jetzt zu viel das auch nochmal aufzuschlüsseln.^^

Da geht es auch darum das eine BasisKlasse einen funktions-Wrapper hat in dem die Aktion quasi drin liegt, was passiert wenn man den Button oder dieses Label anklickt, etc.

Code: Alles auswählen

class Button : public Component {
 ...
  using Callback = std::function<void()>;
  Callback mCallback;
 
  void setCallback(Callback callback) { mCallback = std::move(callback);}
 ...
};

backButton->setCallback(std::bind(&SettingState::requestStackPop, this));
Hier wäre SettingState der Zustand bei einem Einstellungsmenü und requestPop löscht das letzte Element im Stack. (BackButton -> halt ein zurück Knopf^^)
Dies jedoch wird nicht mit der Commandqueue gelöst

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von nufan » Sa Okt 29, 2016 5:49 pm

Ich meine damit, dass ein Command eine action hat, die eine SceneNode als Parameter bekommt:

Code: Alles auswählen

struct Command {
  Category                           category;
  std::functional<void(SceneNode&)>  action;
}
Und du rufst die action wie folgt auf:

Code: Alles auswählen

...
  //im aktiven SceneNode
  onCommand(const Command& command) {
    if (command.category == getCategory())   //abfrage ob der aktuelle SceneNode, der jetzt ausführende der Aktion ist
      command.action();
}
Wo ist der SceneNode-Parameter geblieben?

PietCN
Beiträge: 13
Registriert: Sa Jan 16, 2016 10:49 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von PietCN » Sa Okt 29, 2016 10:32 pm

achso, dies ist in der Aircraft Definition mit drin:

Code: Alles auswählen

class Aircraft {
 ...
  Command mDropPickupCommand;
 ...
};

Aircraft::Aircraft(Type type, ...) {
 ...
  mDropPickupCommand.category = Category::SceneAirLayer;  //Bestimmung der Kategory zum Air-Layer
  mDropPickupCommand.action = [this] (SceneNode& node) {    //Die Zuweisung des Commands
    createPickup(node);
  }
 ...
}

void Aircraft::updateCurrent(Time dt, CommandQueue& commands) {   //update-Methode
 ...
  checkPickUpDrio(commands);
 ...
}

void Aircraft::checkPickUpDrop(Commandqueue& commands) {
 ...
  commands.push(mDropPickupCommand);
 ...
}

void Aircraft::createPickup(SceneNode& node) const {
 ...
  std::unique_ptr<Pickup> pickup(new Pickup(type));    // der Typ wird vorher zufällig bestimmt
  
  node.attachChild(std::move(pickup));                        // Pickup als neuen Kind-Knoten zum SceneAirLayer hinzufügen
}
Das ganze wird dann von der darüber gelegten Klassen die das alles verwaltet gehandelt.
Dabei ist mir jetzt ein kleiner Gedankenfehler aufgefallen... Die Kollision mit einem Pickup wird natürlich nicht in die Commandqueue übergeben, wäre ja auch Blödsinn, da ja in meiner Tabelle die Funktion gespeichert ist. Hier hab ich jetzt nur die Schritte hinzugefügt wie ein Pickup erstellt wird.

Code: Alles auswählen

void Pickup::apply(Aircraft& player) {
  Table[mType].action(player);             //hier wird dann wirklich die entsprechende std::function ausgeführt, 
                                                           //mType ist wie immer ein enum-Element welches den Typ bestimmt und so das entsprechende Pickup in der Datentabelle gefunden werden kann
                                                          //Table wird vorher initialisiert, siehe im oberen Beitrag von mir
Ich bin hier jetzt schon bei über 60 verschiedenen Dateie SourceCode und HeaderDateien. Die meiste Zeit bin ich eigentlich nur noch am suchen, wo war das nochmal und wie war das nochmal. Aber es funktioniert ^^

muss ich jetzt an der Stelle an der ich action aufrufe meine Aircraft-Klasse mit geben, weil ich ja darin eine Methode der Aircraft-Klasse gebunden habe ?

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Erklärung der Funktion std::bind und der std::placeholde

Beitrag von nufan » So Okt 30, 2016 12:59 am

PietCN hat geschrieben:muss ich jetzt an der Stelle an der ich action aufrufe meine Aircraft-Klasse mit geben, weil ich ja darin eine Methode der Aircraft-Klasse gebunden habe ?
Ja, weil du einen Placeholder im Binding hast, der für den this-Zeiger der Aircraft-Methode steht.

Antworten