Schweres Prob mit Vererbung



  • Hey,

    ich hatte eine Klasse "Path" erstellt:

    class Path : private string // ob hier private oder public ist - der Fehler ist immer der selbe :-/
    {
    	public:
            //...
            virtual string operator+( const string& appendix ) const // egal ob virtual oder nicht... die Fehlermeldung ist die gleiche! 
            //...
    }
    

    Ich wollte also den operator+ überschreiben, damit man an den Pfad etwas anhängen kann. mein operator+ beachtet noch andere Dinge, wie z.B. den Verzeichnis-Trenner / bzw. \ ...

    Wenn ich diese Klasse in einer anderen Datei benutze spuckt der g++ die verwirrendste Meldung aus, die ich je gesehen habe:

    ./main2.cc:48: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
    /usr/include/c++/4.2.1/bits/basic_string.h:2109: note: candidate 1: std::basic_string<_CharT, _Traits, _Alloc> std::operator+(const std::basic_string<_CharT, _Traits, _Alloc>&, const _CharT*) [with _CharT = char, _Traits = std::char_traits<char>, _Alloc = std::allocator<char>]
    ./../paths.h:99: note: candidate 2: virtual std::string Path::operator+(const std::string&) const

    Er scheint zu zweifeln, ob er den operator+ von "string" oder den von "Path" nehmen soll, auch, wenn ich die Vererbung von string private mache...! Und selbst bei public-Vererbung wundert es micht - ich hatte gehofft, den operator+ von string in path überschrieben zu haben.

    Bitte helft einem Verwirrten 😉
    Greetz!



  • class Path : private string 
    {
        public:
            //...
            Path operator+( const Path& appendix );
    }
    

    private-Vererbung bedeutet, dass Path durch String implementiert ist, aber keine wirkliche Vererbung ist, d.h. Path ist kein String.

    Die Stringklasse definiert den + Operator nicht als virtual.

    Du willst mit dem + etwas ans Objekt anfügen deswegen darf es nicht mit const markiert werden.

    btw die Fehlemeldung wahr doch als du die public-Vererbung benutzt hast, und dann ist die Frage, ob er + von String oder Path benutzten soll.



  • Zeus schrieb:

    Du willst mit dem + etwas ans Objekt anfügen deswegen darf es nicht mit const markiert werden.

    Nein, ich wollte das schon so machen, dass ein temporäres Objekt zurückgegeben wird:

    cout << MyPath + "README" << endl; // MyPath soll unverändert sein
    

    Außerdem verstehe ich nicht: Wenn ich also privat vererbe, warum sieht der g++ immer noch 2 Möglichkeiten?



  • Welche g++-Version benutzt du denn? Nur weil sich das Ding g++ schimpft, muß es nicht Recht haben. 😉

    Übrigens, von std::string zu erben ist eh gefährtlich, da diese Klasse keinen virtuellen Dtor hat. Du darfst eh nur private und ich glaube protected implementieren.



  • Artchi schrieb:

    Welche g++-Version benutzt du denn? Nur weil sich das Ding g++ schimpft, muß es nicht Recht haben. 😉

    gcc version 4.2.1 (SUSE Linux) - also wirklich nen ganz neuen

    Artchi schrieb:

    Übrigens, von std::string zu erben ist eh gefährtlich, da diese Klasse keinen virtuellen Dtor hat. Du darfst eh nur private und ich glaube protected implementieren.

    Hmm wie könnte ich es sonst sinnvoll anstellen? ein Subobjekt vom Typ String einbauen? Dann könnte ich das ding aber wieder nicht wie einen string benutzen (z.B. length(), cout ...)



  • komposition.

    class MyString;
    
    std::ostream& operator<<(std::ostream&,const MyString&);
    
    class MyString
    {
        private:
            std::string str;
    
            friend std::ostream& operator<<(std::ostream&, const MyString &);
        public:
          //...hier alle stringfunktionen weiterleiten(ja, ärgerlich)
    };
    
    std::ostream& operator<<(std::ostream& stream,const MyString& str)
    {
        return(stream<<str.str);
    }
    

    Ich weis, ist nicht schön. ansonsten wenn alle Stricke reissen(dir das also die arbeit nicht wert ist), dann erb public und schreib in jegliche Dokumentation, dass man das Objekt nicht über basisklassenzeiger/referenzen killen darf.
    Funktioniert natürlich auch nur, solang du keine methoden überschrieben willst, wenn du das willst, kommste quasi nicht um die komposition herum.

    //edit template wirrwarr entfernt, hier nicht nötig


  • Mod

    Die offensichtliche Lösung wäre hier wohl, für alle Überladungen von operator+, die als linken Parameter einen std::string erwarten, ein entsprechendes Gegenstück mit Path anzubieten, um der Mehrdeutigkeit aus dem Weg zu gehen:

    template<class charT, class traits, class Allocator> basic_string<charT,traits,Allocator> operator+(const basic_string<charT,traits,Allocator>& lhs, const basic_string<charT,traits,Allocator>& rhs);
    template<class charT, class traits, class Allocator> basic_string<charT,traits,Allocator> operator+(const charT* lhs, const basic_string<charT,traits,Allocator>& rhs);
    template<class charT, class traits, class Allocator> basic_string<charT,traits,Allocator> operator+(charT lhs, const basic_string<charT,traits,Allocator>& rhs);
    template<class charT, class traits, class Allocator> basic_string<charT,traits,Allocator> operator+(const basic_string<charT,traits,Allocator>& lhs, const charT* rhs);
    template<class charT, class traits, class Allocator> basic_string<charT,traits,Allocator> operator+(const basic_string<charT,traits,Allocator>& lhs, charT rhs);
    

    ➡

    string operator+(const string&) const;
    string operator+(const char*) const;
    string operator+(char rhs) const;
    


  • Vielen Dank für die Hilfe!

    Der operator+ wird ja von mir anders implementiert. Nur - könnte ich nicht den operator<< mit using erlauben?

    Ich hatte gelernt, man kann using nur dann nutzen, wenn man ableitet o_O



  • Nein Public-Vererben darf er schon garnicht, weil seine Basis nicht virtuell ist.
    Das Problem liegt beim Auflösen der richtige Operator

    cout << MyPath + "README";
    

    MyPath ist vom Type Path
    "Readme" ist von Type char*

    es gibt keine passende Operator für diese Typen, also wird char*-> string angewandet und dafür gibst gleich zwei. Warum bin ich nicht sicher, weil eigentlich std::string nicht erreichbare Basis sein dürfte und nicht gefunden werden soll, aber er tut es trotzdem.

    #ifndef PATH_
    #define PATH_
    
    #include <string>
    
    class path : std::string
    {
    public:
    	path(const std::string& str) : _str(str)
    	{
    	}
    
    	virtual std::string operator + (const std::string& str) const
    	{
    		return _str + str;
    	}
    
    	virtual ~path()
    	{
    	}
    
    	std::string _str;
    
    };
    
    #endif
    
    #include <iostream>
    #include <string>
    #include "path.hpp"
    
    using std::string;
    using std::cout;
    using std::endl;
    
    int main()
    {
    	string root("C:\\");
    	path p(root);
    
    	cout << p + string("README") << endl;
    	cout << p + string("") << endl; 
    	return 0;
    }
    

  • Mod

    Zeus@VM(Ubuntu) schrieb:

    Nein Public-Vererben darf er schon garnicht, weil seine Basis nicht virtuell ist.
    Das Problem liegt beim Auflösen der richtige Operator

    cout << MyPath + "README";
    

    MyPath ist vom Type Path
    "Readme" ist von Type char*

    es gibt keine passende Operator für diese Typen, also wird char*-> string angewandet und dafür gibst gleich zwei. Warum bin ich nicht sicher, weil eigentlich std::string nicht erreichbare Basis sein dürfte und nicht gefunden werden soll, aber er tut es trotzdem.

    1. die Operatoren für basic_string sind freie Funktionen, werden daher nicht verdeckt (und der ::std ist ein assozierter Namensraum [da auch hier die Art der Vererbung unwesentlich ist], das Weglassen eines vermutlich vorhandenen using namespace std; wird nicht helfen).
    2. Überladungsauflösung findet vor Zugriffskontrolle statt.



  • Die Lösung liegt ja auch nicht daran das Einfügen von nur gebrauchten Objekten, das ist etwa mein Codestyle, sondern, dass explizierte Umweandel von char* auf string, so dass die richtige + Operation benutzt wird, und wenn du das Ausprobiert, dann weissu dass das Fehlerfrei ist 😉



  • Zeus@VM(Ubuntu) schrieb:

    Nein Public-Vererben darf er schon garnicht, weil seine Basis nicht virtuell ist.
    [...]

    class path : std::string
    

    Standard-Vererbung ist doch pulic, oder?

    Aber egal, wie ich es vererbe, in deinem Bsp wird der Destruktor vom std::string gar nicht aufgerufen, oder?



  • voidpointer schrieb:

    [...]

    class path : std::string
    

    Standard-Vererbung ist doch pulic, oder?[/quote]

    In C++ ist immer bei der Vererbung der Zugriffmodifikator anzugeben

    voidpointer schrieb:

    Aber egal, wie ich es vererbe, in deinem Bsp wird der Destruktor vom std::string gar nicht aufgerufen, oder?

    Ja. Oder wie auch in einigen Fachbüchern am Rande erwähnt wird: Niemals von std::string ableiten.

    cu André



  • asc schrieb:

    In C++ ist immer bei der Vererbung der Zugriffmodifikator anzugeben

    Es geht auch ohne - in dem Fall ist der Standardwert der selbe wie bei der Zugriffskontrolle (public bei struct's, private bei class'es).



  • @voidpointer, du hast Schreibfehler in deiner Signatur:
    [cpp]virtual const void* voidpointer (/*void*/) { return static_cast<const void*>(0); }[/cpp]
    Und (void) gibt man in C++ NICHT als Parameter an (dies macht man nur in C, da () gleichbedeutend ist mit beliebig vielen Parametern, d.h wie in C++ ...).

    Edit: warum klappt das Syntax-Highlighten nicht (wegen dem Fettdruck)?



  • Th schrieb:

    Edit: warum klappt das Syntax-Highlighten nicht (wegen dem Fettdruck)?

    japp, hat mich auch schon oft geärgert.



  • Th schrieb:

    Und (void) gibt man in C++ NICHT als Parameter an (dies macht man nur in C, da () gleichbedeutend ist mit beliebig vielen Parametern, d.h wie in C++ ...).

    Thnx wegen den Fehlern. Das mit dem void ist aber ok. Es ist so viel lesbarer für alte C-Hasen wie mich 🙂 Kp warum die C++ler das gute von C alles wieder abwerten... In C gab es zum Beispiel Funktionszeiger, bei denen ein wichtiger Unterschied zwischen () und (void) bestand. Btw: Was ist an dem NULL falsch?

    Das mit dem String scheint irgendwie ein Designfehler von C++ zu sein. Wie konnten die so etwas wichtiges wie den virtuellen Konstruktor vergessen?

    Ich hatte mir etwas wunderschönes ausgedacht: Ich hatte einen Pfad, der vom String erbt, aber sich anders initialisiert. Bei vielen Pfaden sehr sinnvoll. Aber jetzt muss ich eine Funktion schreiben, die den Pfad als std::string zurückgibt. Da ich in der Funktion einen temporären String habe und in der Aufrufenden auch einen ist das doch starke Verlangsamung... Wie könnte man es eleganter lösen?



  • warum sollte pfad von string ableiten? das sind doch ganz unterschiedliche dinge die logisch garnichts miteinander zu tun haben.



  • voidpointer schrieb:

    Das mit dem String scheint irgendwie ein Designfehler von C++ zu sein. Wie konnten die so etwas wichtiges wie den virtuellen Konstruktor vergessen?

    Nimm mal an, dass es bei so lange durchgekauten Dingen wie den Bestandteilen der C++ Standardbibliothek relativ wenige Designfehler gibt. Wenn, wie im Fall von std::basic_string<>, ein Destruktor nicht virtuell gemacht wird, hat das seine Gründe, unter anderem führen virtuelle Methoden dazu, dass eine vtable angelegt wird sowie pointer darauf in jedem Objekt der Klasse, was in 99% der Fälle nicht benötigt wird. Diesen Overhead einzubauen für ein paar wenige, die wie du von std::string einbauen möchten, war in den Augen des Standardisierungskommitees wohl unangemessen.
    Du könntest dir natürlich recht schnell eine Wrapperklasse schreiben, die einen virtuellen Destruktor anbietet und sonst im Interface mit std::string identisch ist (wenn du es denn als sinnvoll ansiehst, die über 100 Methoden alle mit abzubilden, mehr da zu im GotW 84). Du kannst so ziemlich jede einzelne der Methoden zum einzeiler-inliner machen, ist reine Fleißarbeit.



  • pumuckl schrieb:

    Nimm mal an, dass es bei so lange durchgekauten Dingen wie den Bestandteilen der C++ Standardbibliothek relativ wenige Designfehler gibt.

    naja, gerade über std::string lässt sich hier vorzüglich streiten. nicht wenige leute finden sein interface... nunja, übervoll. (eine memberfunktion find_first_not_of?)

    voidpointer schrieb:

    Das mit dem String scheint irgendwie ein Designfehler von C++ zu sein. Wie konnten die so etwas wichtiges wie den virtuellen Konstruktor vergessen?

    wieso willst du mit std::string polymorph arbeiten? ableiten heißt nicht unbedingt, dass du immer über die basisklasse auf die abgeleitete zugreifen kann.

    (abgesehen davon ist Pfad kein String sondern höchstens implementiert als einer)


Log in to reply