Merfachvererbung



  • @Schlangenmensch sagte in Merfachvererbung:

    Abgesehen davon, das @DocShoe recht hat und man das wohl bessser nicht durch eine Vererbungsstruktur abbildet, gäbe es Virtual Inheritance, was solche Probleme löst:

    class InCall : virtual public PhoneNr
    
    class OutCall : virtual public PhoneNr
    

    Virtual inheritance wäre hier nun wirklich total verkehrt. Das ganze über Vererbung zu modellieren ist Quatsch, das ist klar. Aber wenn man es aus irgend einem Grund trotzdem unbedingt tun will, dann braucht man hier ganz klar zwei getrennte PhoneNr Sub-Objekte. Weil halt die eingehende Telefonnummer selten gleich der ausgehenden Telefonnummer ist.



  • @hustbaer Ja, da gebe ich dir Recht.

    Ich bin irgendwie davon ausgegangen, dass es primär darum ging sich mit Mehrfachvererbung in C++ auseinander zu setzen und das dafür ein eher unpassendes Beispiel drum herum konstruiert wurde. Daher ging es mir darum das Sprachfeature der Virtuellen Vererbung als Lösung des Dread Diamond Problems anzubringen.



  • @C-Sepp Was man machen könnte: man könnte private Vererbung einsetzen. Aber, ich glaube, ich habe das tatsächlich noch nie benutzt:

    
    #include <iostream>
    using namespace std;
    class PhoneNr
    {
    private:
      string phonenr;
    
    public:
      PhoneNr(string phnr = "0") : phonenr(phnr) {}
    
      string getphonenr() const
      {
        return phonenr;
      }
    
      void setphonenr(string phnr)
      {
        phonenr = phnr;
      }
    };
    
    class InCall : public PhoneNr
    {
      bool phcrecorded;
    
    public:
      InCall(const string& inNr) :
        PhoneNr(inNr),
        phcrecorded(false) {}
    
      bool getflag() const
      {
        return phcrecorded;
      }
    
      void setflag(bool phcrc)
      {
        phcrecorded = phcrc;
      }
    };
    
    class OutCall : public PhoneNr
    {
      bool cncestablished;
    
    public:
      OutCall(const string& outNr) :
      PhoneNr(outNr),
      cncestablished(false) {}
    
      bool getflag() const
      {
        return cncestablished;
      }
    
      void setflag(bool cncest)
      {
        cncestablished = cncest;
      }
    };
    
    class PhoneCall : InCall, OutCall
    {
    public:
      PhoneCall(const string& inNr, const string& outNr) :
        InCall(inNr),
        OutCall(outNr){}
    
      void printNrs() const
      {
        cout << "in nr: " << InCall::getphonenr() << "\n";
        cout << "out nr: " << OutCall::getphonenr() << "\n";
      }
    };
    
    int main()
    {
      const PhoneCall phc ("in number", "out number");
      phc.printNrs();
    
      return 0;
    }
    

    Abgesehen, dass die Modellierung zwischen Call und PhoneNr nicht stimmt, könnte man anstatt von privater Vererbung auch gleich Member Variablen einsetzen. Das ist halt mehr oder weniger für den Fall, dass das eine Übungsaufgabe irgendeines Lehrers ist, der auf Vererbung besteht.

    Außerdem, Parameterlose Funktionen brauchen in C++ kein void als "Parameter".

    Aber, wenn es freigestellt ist, wie du das ganze löst, würde ich über ein Redesign nachdenken. Ein Anruf hat mindestens 2 Teilnehmer (können aber auch mehr sein, oder). Jeder Teilnehmer hat eine Telefonnummer. Ein Anruf kann eventuell aufgezeichnet werden (aber, dass nur eine Seite aufgezeichnet wird, wäre zumindest mir unbekannt).



  • @hustbaer sagte in Merfachvererbung:

    ...
    Weil halt die eingehende Telefonnummer selten gleich der ausgehenden Telefonnummer ist.
    ...

    Vielleicht kann man die hier als EndPoint sehen, da sie zum Hören der InCall ist und zum Sprechen der OutCall. Allerdings werden für ein vollständiges Call-Objekt die gleichen Informationen für die Gegenstelle benötigt.



  • Vielen dank für die Ratschläge. Dann weis ich auch gleich wie die Übungsaufgabe eigentlich abgewandelt werden sollte.

    In einer weiteren Aufgabe dazu geht es darum die Funktion dial() zu schreiben:
    "Dial() erhält als Argument eine anzurufende Telefonnummer. Falls die Sitzung initialisiert wurde und die Nummer des angerufenen Telefonanschlusses "0" ist, setzt die Methode das Flag zum Verbindungsaufbau auf true"
    Die Lösung dazu sieht folgendermaßen aus:

    bool PhoneCall::dial(const string& n)
    {
        if (InCall::getphonenr()=="0" && OutCall::getphonenr() !="0")
        {
            InCall::setphonenr(n);
            OutCall::setflag(true);
            return true;
        }
        else return false;
    }
    

    Warum kann in dem Fall der Wert von phonenr über zwei Funktionen getphonenr() in der Klasse InCall und
    OutCall abgefragt werden? Sicherlich gibt es ja zwei Version von getphonenr() in beiden Klassen aufgrund
    der Vererbungshierarchie, trotzdem greifen doch beide Versionen auf das selbe Datenelement phonenr
    zu, oder wie kann man sich das im Speicher vorstellen? Danke!



  • @C-Sepp Wenn das eine Übungsaufgabe ist, zu der du eine Musterlösung hast, wäre es hilfreich, wenn du die zusammen postest. Woher sollen wir denn so wissen, wie da tatsächlich die Vererbungshierarchie aussieht und was laut Aufgabe gefordert wurde?

    Wenn das mittels privater Vererbung gelöst wurde, könnte das so funktionieren, bei virtueller Vererbung nicht.

    Wie gesagt, poste den gesamten Code, sonst ist das alles nur geraten 😉



  • @C-Sepp
    Ist das zufällig ein C++ "Buch" von Jürgen Wolf?



  • @DocShoe sagte in Merfachvererbung:

    Ist das zufällig ein C++ "Buch" von Jürgen Wolf?

    Da könnte man auch fragen "Hast du dir einen Wolf programmiert?" 😂

    SCNR



  • 😃 Ne zum Glück nicht. Moment...ich poste mal die komplette Aufgabenstellung dazu



  • Die Software einer Telekommunikationsgesellschaft soll Telefonverbindungen erfassen. Es sind die Telefonnummern von Anrufern und Empfängern sowie der Auf- und Abbau von Verbindungen zu simulieren.

    Definieren Sie zu dem Zweck eine Basisklasse PhoneNr zur Darstellung einer Telefonnummer, die nur ein Datenelement vom Typ string besitzt. Es gibt nur einen Dafault-Konstruktor, der die Telefonnummer auf "0" setzt, sowie Zugriffsmethoden, die die Telefonnummer setzen bzw. lesen.

    Die Klassen InCall und OutCall entstehen aus PhoneNr durch public Vererbung. OutCall besitzt ein zusätzliches boolsches Flag flag, das anzeigt, dass eine Telefonverbindung aufgebaut wird. InCall besitzt ein zusätzliches Flag flag, das anzeigt, dass ein Telefonanruf angenommen wurde, dass heißt dass eine Verbindung besteht. Beide Klassen besitzt einen Default-Konstruktor, der das jeweilige Flag false setzt und Zugriffsmethoden getflag() und setflag() zum Lesen bzw. Schreiben des jeweiligen Flags.

    Die Klasse PhoneCall entsteht aus InCall und OutCall durch public-Vererbung. Sie besitzt kein zusätzliches Datenelement und folgende Methoden:
    1.InitCall() setzt die als Argument übergebene Telefonnummer eines Anrufers, falls seine gespeicherte Nummer "0" ist, und initialisiert so eine Sitzung.
    2. dial().....so wie bereits beschrieben

    Die Lösung dazu unterscheidet sich nicht zu meinen Code, den ich gestern gepostet habe. Nur die Funktion dial() muss wiegesagt neugeschrieben werden.



  • Uff 😞

    Haste nen Link zur Aufgabe? Oder den Namen des Buches?



  • wozu noch der Link...das ist doch schon die komplette Aufgabe



  • Nen Link habe ich leider nicht



  • @C-Sepp sagte in Merfachvererbung:

    wozu noch der Link...das ist doch schon die komplette Aufgabe

    Würde ganz gerne sehen, welche Aufgaben da sonst noch so gestellt werden.
    Wie heisst denn das Buch oder wasauchimmer?


  • Mod

    Krasses Versagen des Buches, ich würde das nämlich einen solch groben Modellierungsfehler nicht so entspannt sehen wie manch andere hier. Das ist schon ein Wegwerfgrund. Ganz ernst, ohne Überteibung, denn wer solchen Müll an einer Stelle schreibt, der macht das auch an anderen Stellen. @C-Sepp: Du merkst ja vielleicht auch selber, dass du hier eher "dumme" Fragen stellen musst. Das ist nicht normal für Anfänger auf deinem Niveau. Das liegt an dem schlechten Lehrmaterial. Daraus wirst du nur schlechtes C++ lernen, und das wieder zu verlernen ist hinterher viel schwieriger, als es gleich richtig zu lernen. Und neu lernen musst du später sowieso.



  • @C-Sepp
    Vererbung ohne virtual sieht im Speicher so aus als ob jede Basisklasse als ein Member vorliegt, ganz am Anfang des Objekts:

    struct PhoneNr {
        string phonenr;
    };
    
    struct InCall {
        PhoneNr base; // Basisklassen-Subobjekt
        bool phcrecorded;
    };
    
    struct OutCall {
        PhoneNr base; // Basisklassen-Subobjekt
        bool cncestablished;
    };
    
    struct PhoneCall {
        InCall base1;  // 1. Basisklassen-Subobjekt
        OutCall base2; // 2. Basisklassen-Subobjekt
    };
    
    PhoneCall phc;
    

    phc.base1.base und phc.base2.base sind also zwei völlig unabhängige Sub-Objekte von phc, jeweils vom Typ PhoneNr - und die haben natürlich auch jeweils ein eigenes, unabhängiges Member phonenr.



  • Alles klar....die Aufgabe stammt aus den Buch "C++ Das Übungsbuch " von Ulla Kirch und Peter Prinz.
    Nur die Frage zu den 2 "Versionen" von getphonenr() in InCall und OutCall aufgrund der Vererbungshierarchie ist
    geblieben. Dazu habe ich eine Funktion geschrieben in der phonenr mit InCall::setphonenr("Test1") und OutCall::setphonenr("Test2") beschrieben und anschließend mit InCall::getphonenr() usw. ausgelesen wird. Dabei war zu erkennen, dass nur "Test2 in den Speicher geschrieben wurde. Inwiefern macht eine Abfrage der Werte so wie dial():

    bool PhoneCall::dial(const string& n)
    {
        if (InCall::getphonenr()=="0" && OutCall::getphonenr() !="0")
        {
             ...        
        }
        else return false;
    }
    

    dann eigentlich Sinn, geschweige denn eine Vererbung solcher Funktionen von einer Basisklasse zu mehreren abgeleiteten Klassen? Danke!


Anmelden zum Antworten