[c++] Problem bei Funktionsaufruf aus Klasse



  • Hallo Leute,
    ich hab mal wieder ein Problem... 🙂

    Ich habe aus einer Bibliothek 2 Klassen die sich jeweils aus 2 Basisklassen ableiten. Hier die Hierachie:

    1. GABin2DecGenome : public GA1DBinaryStringGenome : public GABinaryStringGenome, public GAGenome
    2. GARealGenome : GAReal : public GA1DArryAllelGenome : public GA1dArryGenome : public GAArray<T>, public GAGenome

    Ich habe nun über eine Abfrage ein Objekt "genome" wie folgt erzeugt:

    GAGenome *genome = NULL;
    switch(genometyp) {
    	case 1 : genome = new GABin2DecGenome(map, objective); break;
    	case 2 : genome = new GARealGenome(alleles, objective); break;
    }
    

    Funktioniert auch wunderbar.
    Ich möchte nun im weiteren Code mittels einer weiteren Abfrage auf die Funktion "float phenotype(unsigned int n) const" der Klasse GABin2DecGenome auf das Objekt genome anwenden. Dazu habe ich folgende Abfrage geschrieben:

    if (genometyp ==1){
    
    *genome = ga->population().individual(0);  
    cout << genome->phenotype(0);
    }
    

    Es wird folgender Fehler ausgegeben :

    `phenotype' undeclared (first use this function)
    

    Ich denk mal, dass es daran liegt das es keine Funktion der Basisiklasse ist, oder? Wie bekomme ich das trotzdem hin? Ich dachte er berücksichtigt das garnicht, wenn die Schleife mit genometyp == 1 abgefragt wird ist das Objekt genome aus der Klasse GABin2Dec und damit die Funktion anwendbar, oder nicht?

    Viele Grüße

    Guddy


  • Administrator

    guddy schrieb:

    Ich denk mal, dass es daran liegt das es keine Funktion der Basisiklasse ist, oder?

    Korrekt.

    guddy schrieb:

    Wie bekomme ich das trotzdem hin?

    Du könntest einen dynamic_cast anwenden:

    GABin2DecGenome* bin2DecGenome = dynamic_cast<GABin2DecGenome*>(genome);
    
    if(bin2DecGenome) // Zeiger gültig?
    {
      /* erfolgreich */
    }
    

    Ich möchte aber noch darauf hinweisen, dass dynamic_cast meistens auf einen Designfehler hinweist. Es gibt Ausnahmen, wo man den Cast benötigt, aber eigentlich höchst selten bis nie.
    In deinem Fall wäre es vielleicht eine Überlegung wert die Funktion zu zerstückeln. Eine wenn ein GABin2DecGenome Objekt kommt und eine für das GARealGenome Objekt. Wenn Methoden aus der Basisklasse aufgerufen werden, welche beide Objekte betreffen würde, kann man dafür gemeinsame Funktionen erstellen. Also irgendwie so:

    void first(GABin2DecGenome* genome)
    {
      /* Irgendwas spezielles */
    
      all(genome);
    
      /* wieder was eigenes */
    
      /* wieder eine allgemeine Funktion? */
    }
    
    void second(GARealGenome* genome)
    {
      all(genome);
    
      /* oder eine andere Reihenfolge, das Prinzip sollte klar sein ;) */
    }
    
    void all(GAGenome* genome)
    {
      /* Irgendwas */
    }
    
    // Irgendwo:
    switch(genometyp) {
        case 1 : first(new GABin2DecGenome(map, objective)); break;
        case 2 : second(new GARealGenome(alleles, objective)); break;
    }
    

    Das ist aber nur ein sehr grobes Beispiel, welches das Prinzip verraten soll.

    guddy schrieb:

    Ich dachte er berücksichtigt das garnicht, wenn die Schleife mit genometyp == 1 abgefragt wird ist das Objekt genome aus der Klasse GABin2Dec und damit die Funktion anwendbar, oder nicht?

    1. Ist das keine Schleife, sondern eine If-Anweisung.
    2. Es gibt keinen Zusammenhang zwischen dem Wert genometyp und dem Objekt auf welches der Zeiger genome zeigt. Und genome zeigt ganz klar auf ein Objekt der Basisklasse und nichts anderes.
    3. Würde der Compiler dem Standard widersprechen, wenn er das durchgehen lassen würde.

    guddy schrieb:

    if (genometyp ==1){
     
    *genome = ga->population().individual(0); // (1)
    cout << genome->phenotype(0);
    }
    

    Mir gefällt da aber vor allem diese Zeile (1) nicht. Bist du dir bewusst was du da machst? Du kopierst nur den GAGenome Teil vom Wert, welcher ga->population().individual(0) zurückgibt und der Rest wird nicht kopiert. Es wird somit nur eine Teilkopie ausgeführt.

    Grüssli



  • Hi,
    bin bis jetzt noch über keinen Fall gelaufen wo man den dynamic_cast gebraucht hätte. Kannst du mir ein Beispiel dafür geben, wo du ihn wirklich gebraucht hast?

    Gruß Andy


  • Administrator

    Andy2211 schrieb:

    bin bis jetzt noch über keinen Fall gelaufen wo man den dynamic_cast gebraucht hätte. Kannst du mir ein Beispiel dafür geben, wo du ihn wirklich gebraucht hast?

    ... ehm ... auf Anhieb ... nein 🙂
    Ich habe ihn glaub ich ein einziges Mal wirklich eingesetzt, als ich auch eine Basisklasse hatte und genau wusste, von welchem Typ das Objekt eigentlich ist. Ein static_cast war aber nicht möglich, da der Typ von mehreren Klassen erbte. Aber um ehrlich zu sein, finde ich den Code dazu nicht mehr und ich weiss auch nicht mehr, wieso es dort nicht anders ging. Und wenn ich den Code nicht mehr finde, könnte es fast noch sein, dass ich am Ende doch eine andere Lösung gefunden hatte 🙄

    Ein Grund, wieso man den dynamic_cast verwenden könnte, wäre vielleicht wegen eine schlecht gemachten Bibliothek. Welche nur ein Basisobjekt aktzeptiert und es irgendwo wieder zurückgibt, man dort aber den abgeleiteten Typ benötigt. Also quasi zur Behebung von Designfehlern von anderen 😃

    Grüssli



  • Pointer casten tu ich hier den ganzen Tag lang fröhlich, dynamic_cast ist dabei einfach die "sicherste" Option, da es eine Überprüfung zur Laufzeit zur Folge hat und entsprechend sinnvolle Fehlermeldungen gibt, anstatt fröhlich weiter zu rattern.



  • Danke Dravere, das hat mich auf jeden Fall weitergebracht.

    Mir gefällt da aber vor allem diese Zeile (1) nicht. Bist du dir bewusst was du da machst? Du kopierst nur den GAGenome Teil vom Wert, welcher ga->population().individual(0) zurückgibt und der Rest wird nicht kopiert. Es wird somit nur eine Teilkopie ausgeführt.

    Ich denk schon, indvidual() gibt eine Referenz auf ein Objekt der Klasse GAGenome zurück.
    Ich möchte ungern die Funktion zerstückeln, da das Programm auch von anderen Usern mit derselben Bibliothek nutzbar seien soll.

    Kannst du mir mal den unterschied zwischen statischem und dynamischem cast erklären?

    Grüße

    Guddy



  • Bei diesem Problem habe ich bisher auch keine andere Lösung gefunden als dynamisch zu casten. (Ich schreibe die aktuelle Lösung mit dem dynamischen Cast später noch in den genannten Thread)

    Oft kann man dynamische Casts durch statische Ersetzen, weil von vornherein klar ist, welcher Typ vorliegt. Ein Beispiel wäre wieder einmal boost::any ( if (data->type() == typeid(Class)) { static_cast< Class* > } else { throw bad_any_cast } ).

    EDIT:

    guddy schrieb:

    Kannst du mir mal den unterschied zwischen statischem und dynamischem cast erklären?

    Der statische Cast erzeugt bereits zur Compilezeit Code zur Umwandlung. Sprich, wenn Du einen Basisklassenzeiger statisch auf den einer abgeleiteten Klasse castest, wird Code erzeugt, der diesen Zeiger u.U. verschiebt (weil Basisobjekt und abgeleitetes Objekt unterschiedliche Startadressen haben können). Im Binärcode steht dann nur noch z.B. neuerzeiger = alterzeiger + 4 . Ist dieses Basisobjekt allerdings in Wirklichkeit von einem anderen abgeleiteten Typ, läuft das Programm fröhlich mit einem falschen Zeiger weiter.

    Ein dynamischer Cast erzeugt darüberhinaus Code, der zur Laufzeit in einer Tabelle nachschaut, ob das Basisobjekt wirklich von diesem fraglichen abgeleiteten Typ ist, und gibt einen Nullzeiger zurück, falls dem nicht so ist (oder wirft eine Exception, wenn man Referenzen castet).


  • Administrator

    Ich ändere mal schnell die Reihenfolge deines Satzes Fellhuhn.

    Fellhuhn schrieb:

    ... dynamic_cast ist dabei einfach die "sicherste" Option, da es eine Überprüfung zur Laufzeit zur Folge hat und entsprechend sinnvolle Fehlermeldungen gibt, anstatt fröhlich weiter zu rattern.

    Das hat niemand hier bestritten, das ist wahr. Wobei die sinnvolle Fehlermeldung auch ein Nullzeiger sein kann, was einen unschönen Absturz zur Folge hat.

    Fellhuhn schrieb:

    Pointer casten tu ich hier den ganzen Tag lang fröhlich, ...

    Das dagegen ist eher schlecht. Ein Downcast, also von einer Basisklasse zu einer abgeleiteten Klasse, sollte so gut wie nie vorkommen. Wenn du die ganze Zeit am Casten bist, dann hast du Fehler im Design! Klar ist es nicht verboten, aber meistens eher ungeschickt und teilweise auch Fehleranfällig.

    Und noch eine Ergänzung zu LordJaxoms erklärung:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-39493.html
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-39463.html

    Zudem, ein static_cast ist nicht möglich, wenn der Typ, zu welchem man casten möchte, mehrere Basisklassen besitzt. Dann muss in jedem Fall dynamic_cast eingesetzt werden.

    Grüssli



  • Zudem, ein static_cast ist nicht möglich, wenn der Typ, zu welchem man casten möchte, mehrere Basisklassen besitzt. Dann muss in jedem Fall dynamic_cast eingesetzt werden.

    Ein static_cast ist in vielen Fällen dann trotzdem möglich. Ein Fall wo es nicht geht ist wenn man virtuelle Basisklassen hat (reicht dann aber schon eine AFAIK). Und ich glaube auch wenn die selbe Klasse in der Hirarchie mehrfach auftaucht ("dreaded diamond" ohne virtuelle Vererbung).
    Beim "dreaded diamond" ohne virtuelle Vererbung kann man aber, wenn man weiss welches Unterobjekt man genau hat, mit 2 static_cast arbeiten um dem dynamic_cast zu entkommen.


  • Administrator

    Oh, stimmt. Mein Fehler. Ich ziehe meine letzte Aussage zurück 🙂
    Und ja, eine virtuelle Basisklasse und es geht nicht mehr (auch gleich mit MSVC 2005 getestet). Und bei dreaded diamond ist das sowieso eine komplexe Sache. Da kann man ja nicht mal einen normalen Upcast durchführen.

    Etwas Code für guddy oder andere:

    // virtual base class
    class Base
    { };
    
    class Test : public virtual Base
    { }
    
    int main()
    {
      Base* base = new Test; // normal.
    
      // Test* test = static_cast<Test*>(base); geht nicht!
      Test* test = dynamic_cast<Test*>(base); // geht einwandfrei!
    
      delete test;
    
      return 0;
    }
    
    // dreaded diamond
    
    class Base
    { };
    
    class Derv1 : public Base
    { };
    
    class Derv2 : public Base
    { };
    
    class Test : public Derv1, public Derv2
    { };
    
    int main()
    {
      Base* base = static_cast<Test::Derv1*>(new Test); // Lustiger Upcast :)
    
      // Test* test = static_cast<Test*>(base); geht nicht!
      Test* test = static_cast<Test*>(static_cast<Derv1*>(base)); // geht!
    
      delete test;
    
      return 0;
    }
    

    Was eine virtuelle Basisklasse und/oder dreaded diamond genau ist, solltet ihr anderswo nachlesen. Es ist aber wohl eher ein fortgeschrittenes Konzept der Sprache.

    Grüssli


Anmelden zum Antworten