Operator << überladen fehlgeschlagen



  • Hallo,

    ich schreibe gerade eine ganz einfache Klasse "Uhrzeit" (über die Sinnhaftigkeit dieser Klasse soll es hier nicht gehen)

    und möchte den << operator überladen, der Compiler gibt aber den Fehler aus:

    ../uhrzeit.h:30: error: expected constructor, destructor, or type conversion before '&' token
    

    Meine Klasse sieht so aus:

    #ifndef UHRZEIT_H_
    #define UHRZEIT_H_
    
    class Uhrzeit {
    public:
    	Uhrzeit(int& std_, int& min_) : std(std_), min(min_) { }
    	Uhrzeit() { std=-1; min=-1; }
    
    	void set(const int& s, const int& m) { std=s; min=m; }
    
    	int getStd() const { return std; }
    	int getMin() const { return min; }
    
    	void clear() { std=-1; min=-1; }
    	virtual ~Uhrzeit() { }
    
    private:
    
    	int std;
    	int min;
    };
    
    ostream& operator<<(ostream& ostr, Uhrzeit& rhs) {
    return ostr << "hier kommt der Ausgabetext";
    }
    
    #endif /* UHRZEIT_H_ */
    

    Was habe ich falsch gemacht? Eine .cpp Datei habe ich bisher nicht.



  • also erstmal sehe ich keinen fehler...
    und solange alle header eingebunden sind und du nicht auf private member zugreifst von deinem << operator sollte auch alles ok bleiben...

    das ist doch nicht der origninale fehler code, oder?!
    wenn doch, schreib mal die genaue fehlermeldung



  • Ist denn std::ostream im Header bekannt?



  • Also ich habe mal aus meinem ganzen Programm diese Header Datei rauskopiert und ein einfaches test-cpp-file erstellt.

    Die Header Datei hat sich nicht verändert, hier ist meine cpp-Datei:

    #include "uhrzeit.h"
    #include <iostream>
    
    using namespace std;
    
    int main() {
    
      Uhrzeit t;
      t.set(23,50);
      cout << t;
    
    }
    

    Fehler ist:

    In file included from test.cpp:2:                                                                             
    uhrzeit.h:30: error: expected constructor, destructor, or type conversion before '&' token                    
    test.cpp: In function 'int main()':                                                                           
    test.cpp:11: error: no match for 'operator<<' in 'std::cout << t'                                             
    /usr/include/c++/4.3/ostream:112: note: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>& (*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]       
    /usr/include/c++/4.3/ostream:121: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]               
    /usr/include/c++/4.3/ostream:131: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char, _Traits = std::char_traits<char>]                                                   
    /usr/include/c++/4.3/ostream:169: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char, _Traits = std::char_traits<char>]                                                                             
    /usr/include/c++/4.3/ostream:173: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]                                                                    
    /usr/include/c++/4.3/ostream:177: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/bits/ostream.tcc:97: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:184: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/bits/ostream.tcc:111: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:195: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:204: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:208: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:213: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:217: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:225: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:229: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/bits/ostream.tcc:125: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_streambuf<_CharT, _Traits>*) [with _CharT = char, _Traits = std::char_traits<char>]
    

    Der zweite Fehler basiert ja darauf, dass ich keinen <<-Operator für Uhrzeit habe, also lässt sich das Problem auf den ersten Fehler reduzieren, denke ich.

    EDIT: Habe jetzt erst den Post danach gesehen.
    Das war natürlich ein Fehler (das kommt davon, wenn man so oft using namespace std benutzt.

    Jedoch kommt der gleiche Fehler immernoch 😞

    Jetzt sieht die Methode so aus:

    std::ostream& operator<<(std::ostream& ostr, Uhrzeit& rhs) {
    	return ostr << "Dies ist der Text";
    }
    

    Das ist übrigens das ganze Header-File, es ist nichts inkludiert oder so.



  • dann inkludier mal schleunigst iostream oder wenigstens ostream
    (using namespace niemals im header, dadurch hebelst du das gesamte namespace system aus!)

    und nochwas: bei deinem << operator: übergib dein uhrzeit objekt per const&



  • Ah ok, das war es!

    Jetzt kommt der Fehler, dass diese Methode schon einmal definiert wurde, obwohl in meinem ganzen Projekt-Ordner diese Methode nirgendwo überschrieben habe 😞

    ./logik.o: In function `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Uhrzeit&)':
    /home/lumbeck/Cworkspace/WM/Debug/../uhrzeit.h:35: multiple definition of `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Uhrzeit&)'
    ./Workminimizer.o:/home/lumbeck/Cworkspace/WM/Debug/../uhrzeit.h:35: first defined here
    ./main.o: In function `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Uhrzeit&)':
    /home/lumbeck/Cworkspace/WM/Debug/../uhrzeit.h:35: multiple definition of `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Uhrzeit&)'
    ./Workminimizer.o:/home/lumbeck/Cworkspace/WM/Debug/../uhrzeit.h:35: first defined here
    collect2: ld returned 1 exit status
    make: *** [WM] Error 1
    


  • erstmal, kompilier mal alles komplett neu und linke es.
    inklude guards ahst du, somit könntest du die datei auch mehrfach einbinden und es sollte funktionieren...
    mh ansonsten sehe ich keinen fehler



  • Der op<< ist im Header definiert. Wenn der Header jetzt in mehrere Übersetzungseinheiten eingebunden wird (wie hier in logik.cpp und main.cpp), ist er eben auch in mehreren ÜE definiert und das ist der Fehler, den der Compiler uns da sagt. Lösung: entweder den op<< im Header nur deklarieren und im uhrzeit.cpp definieren oder im header mit inline definieren. (Freie Funktionen wie der op<< sind im Gegensatz zu Klassenmethoden nicht implizit inline)



  • Achso, ich dachte, um dieses Methodenüberschrieben zu verhindern, ist das #ifndef HEADER_H da.

    Mit inline funktioniert es auf jeden Fall 🙂



  • Fabulus schrieb:

    Achso, ich dachte, um dieses Methodenüberschrieben zu verhindern, ist das #ifndef HEADER_H da.

    Das ist nur dafür da, dass der Header nicht zweimal in der selben ÜE eingebunden wird, z.B. indirekt über andere Header. Zwei unterschiedÜbersetzungseinheiten werden aber mit zwei völlig unabhängigen Compileraufrufen übersetzt. Der Compiler weiß nicht, ob die Definition in einer anderen ÜE schon vorkommt oder nicht. Und selbst wenn, muss es ja nicht sein, dass die Objektdateien dann auch zusammengelinkt werden.
    Darum ist das auch kein Compilerfehler, sondern ein Linkerfehler: erst der Linker erkennt, dass in beiden Objektdateien eine Definition für den op<< steckt und kann beim Zusammenlinken nicht entscheiden, welche von beiden denn aufgerufen werden soll. Merke: der Linker bekommt deinen Code und die include-Guards garnicht zu sehen, er bekommt nur die Objektdateien.



  • Wieso hast du bei deinem Standardkonstruktor nich die Initialisierungsliste genutzt sondern den Konstrukorrumpf?
    Ich weiß gehört nicht zum eigentlichen Problem, aber das wurde ja eh schon gelöst;-)

    Lg freeG



  • Oh, das habe ich wohl unbewusst gemacht, als ich diesen nachträglich hinzugefügt hab... alte Java-Angewohnheit.



  • Ok, dachte schon du hättest vll gedacht dass man die Init-Liste nur nutzen kann wenn man Argumente hat:D

    Lg freeG



  • Ich denke nicht, dass hier eine Übergabe per Referenz bzw const-Referenz einen Vorteil brint (da int und kein Klassentyp). Möglicherweise macht das das Pogramm sogar langsamer, um ehrlich zu sein weiß ich es aber nicht.



  • wieso sollte es langsamer werden?!?
    bei call by reference wird doch mehr doer weniger nur die adresse übergeben und die ist so groß wie int. ob nun das geklasselte int oder das zeiger int übergeben wird sit doch egal ...



  • Und genau deswegen kann es langsamer werden - um den Wert hinter der Referenz zu bekommen, mußt du jedes Mal diese Adresse auswerten.



  • Selbst wenn du eine Kopie erstellst (Call by Value) muss auch immer noch bei jedem Zugriff auf diese Variable die relative Adresse der Variablen bei Zugriff gesetzt sein und über diese zugegriffen werden.

    Während bei Call by Value also eine harte Kopie erstellt werden muss (1:1 Kopie im lokalen Speicher für diesen Block), wird bei Call by Reference nur ein Zweitname bzw die Adresse übergeben.

    Der Dereferenzierungsaufwand ist somit der gleiche und Call by Reference ist in jedem Fall performanter.



  • Ich bin zwar nicht wirklich Experte für Assambler-Feinheiten (da mußt du etwas tiefer im Forum nachschauen), aber imho kann indirekte Adressierung durchaus langsamer werden als direkte Adressierung*. Außerdem ist ein int so klein, daß er durchaus in ein Register gepackt werden kann - und dann brauchst du überhaupt keine Speicher-Zugriffe mehr, um sie zu verwenden.

    *Bei der Übergabe der Referenz schreibst du die Adresse der Variable in den lokalen Block (was auch nicht weniger Platz einnehmen dürfte als der int-WErt selber) und brauhst anschließend für jede Aktion zwei Lese-Operationen: (1) lies die Adresse der Variablen aus dem Stack, (2) lies den Wert der Variablen von dort, wo die Adresse hinzeigt. Bei der direkten Adressierung entfällt der erste Schritt (wo die lokalen Variablen bezüglich des Stack-Pointers stehen, weiß der Compiler, also muß er diese Adresse nicht mehr irgendwo holen).

    PS: Ja, ich weiß, diese Unterschiede fallen shon unter Mikro-Optimierung 😉



  • Du hast bei Referenzen quasi dieselbe Adressierung als sei es eine normale Variable.

    Viele Mikrooptimierungen machen eine Große! 😃



  • erstie schrieb:

    Du hast bei Referenzen quasi dieselbe Adressierung als sei es eine normale Variable.

    Eben nicht - bei einer "normalen" Variable steht die Adresse zur Compilezeit fest (notfalls relativ zum Stack-Pointer), bei einer Referenz kennst du die Adresse erst zur Laufzeit.
    (effektiv sind Referenzen nicht mehr als gut getarnte Zeiger - der Compiler verbeitet zwar einige typishe Zeiger-Operationen (z.B. Inhalt auslesen) und führt andere (Dereferenzierung) automatisch durch, aber im fertigen Maschinencode wirst du nicht sehr viele Unterschiede zwischen einem int* und einem int& finden)

    Viele Mikrooptimierungen machen eine Große! 😃

    Wenn du es im großen Maßstab betrachtest, eher nicht :D.
    Bei theoretischen Betrachtungen zur Laufzeit fallen konstante Faktoren sowieso unter den Tisch, genauso wie konstanter Overhead (siehe O-Notation). Und in der Praxis sollte man erst an solche Mikro-Optimierungen gehen, wenn man weiß, daß sie in dem Teil des Programms stattfinden, der wirklich die Laufzeit frisst, nicht pauschal bei allen Ecken, die möglicherweise schneller sein könnten.



  • Nein falsch. Du kennst zur Compilezeit nur relative Adressen.
    Der Compiler kann ja nicht wissen wo später das Programm im Speicher liegt.

    Eine Referenz verhält sich nach wie vor wie ein Zweitname auf eine Variable.

    Betrachte dir doch mal genauer, was genau eine Variable ist.

    Speicher, der reserviert wurd und mittels eines Aliasnamen(Variablennamen) über seine Adresse auf den tatsächlichen Speicher dereferenziert wird.

    Vielleicht kannst du ja mal den von dir angeführten maschinencode Posten.


Anmelden zum Antworten