Funktionen und Definition - Trennung in Headern



  • So ihr lieben ...

    da gibts doch tatsächlich noch einen Thread von mir 😃 ich hoffe, ich trolle für euch nicht auch noch rum .... 😕

    Es geht um das Auslagern von Klassenfunktionen in Header Dateien.
    Interessanterweise - ich hätte es nicht erwartet - muss/soll ich die Konstruktoranweisungen auslagern ...
    Aber den Zuweisungsoperator krieg ich nicht raus.

    Header.h

    #ifndef HEADER1_H
    #define HEADER1_H
    
    class A {
    public:
        A(const int dx, const int dy);
        A(const A& einA);
        A &operator=(A einA)  {swapi(einA); return *this;}  // bekommt man das auch ausgelagert?
        void swapi(A& einA);
        int getX() const;
        int getY() const;
    private:
        int x;
        int y;
    };
    
    #endif // HEADER1_H
    

    Header.cpp

    #include "header.h"
    
    A::A(const int dx, const int dy) : x(dx), y(dy) {}
    A::A(const A& einA) :x(einA.x), y(einA.y) {}
    int A::getX() const {return x;}
    int A::getY() const {return y;}
    void A::swapi(A& einA) {
        std::swap(x,einA.x);
        std::swap(y,einA.y);
    }
    

    Eine Frage noch zu den Namensbereichen:
    Alle meine Funktionen - auch wenn sie in Headern ausgelagert sind - sind global in allen Dateien verfügbar? Und das kann das durch static oder namespace einschränken, oder?

    Was ist aber mit globalen Variablen? Sind die nur in der ausgelagerten Datei zugänglich?
    Zb eine std::ofstream Output("output.txt"); möchte ich immer zur Verfügung haben (Anm. Soll da lieber auch ein const vor?).
    Aber der ofstream ist ja - so wie es da steht - inklusive Initialisierung. Wo sollen diese rein, in die Header.h oder in die Header.cpp oder kurz vor main stehen lassen?

    Ein großes Dankeschön! 🙂



  • A &operator=(A einA)  {swapi(einA); return *this;}  // bekommt man das auch ausgelagert?
    

    Gehen wir das logisch an.

    Rückgabetyp: A&
    Parameterliste: (A einA)
    Also muss gelten Funktionsname: operator=

    Im cpp ist es immer:
    Rückgabetyp Klassenname::Funktionsname Parameterliste

    Also

    A &A::operator=(A einA)
    {swapi(einA); return *this;}
    


  • Oh schön! 🙂 Das funktioniert! 👍
    Danke für die tolle Erläuterung 🙂



  • teilantwort schrieb:

    Im cpp ist es immer:
    Rückgabetyp Klassenname::Funktionsname Parameterliste

    Zeig mal wie das aussieht wenn der Rückgabetyp ein Funktionszeiger ist, sagen wir z.B. void(*)(int, int) 🤡



  • hustbaer schrieb:

    teilantwort schrieb:

    Im cpp ist es immer:
    Rückgabetyp Klassenname::Funktionsname Parameterliste

    Zeig mal wie das aussieht wenn der Rückgabetyp ein Funktionszeiger ist, sagen wir z.B. void(*)(int, int) 🤡

    void (*Klassenname::Funktionsname Parameterliste)(int,int) 🤡
    Da würde man aber für den Rückgabetypen ein hoffentlich typedef nehmen, dann stimmt die Aussage sogar wieder.
    typedef void(*Rückgabetyp)(int, int);
    Rückgabetyp Klassenname::Funktionsname Parameterliste



  • Der Linker kümmert sich um die Funktionsdefinitionen, so dass jeder die verwenden kamn.
    Mit static und anonymem namespace kannste das verhindern, dann aber nur für Hilfsfunktionen, dire nur ein Sourcefile braucht. Nie im Header!
    Wenn nur die Klasse A den Output als Hilfsmittel braucht -> anonymer namespace.
    Besser ist es aber den als Parameter mitzugeben, dann kann diemain ändern, wohin ausgegeben wird.
    Kein const bei Streams, dann kannste nichts ausgeben!
    Aproppos Ausgabe: Sagt dir der Steamoutputoperator etwas? Wenn nicht, solltest du dich mal darin ei.lesen.



  • Nathan schrieb:

    Nie im Header!

    Meistens nie.
    Naja.
    Alle Generalisierungen sind falsch. Besonders die mit Rufzeichen!



  • volkard schrieb:

    Nathan schrieb:

    Nie im Header!

    Meistens nie.
    Naja.
    Alle Generalisierungen sind falsch. Besonders die mit Rufzeichen!

    😃



  • Nathan schrieb:

    Steamoutputoperator

    Meinst du, dass ich mit
    Output << variable << "Text";
    das genauso füllen kann wie bei cout? Das ist da ja dann schon überladen.

    Vielen Dank für eure Antworten. 🙂

    Wenn ich jetzt zwei Klassen hätte und die andere Klasse B hätte Objekte von A (oder wäre eine Ableitung). Müssen diese Klassen in die selbe Datei oder in andere Headern?
    Ich hab probeweise die erste Klasse ausgelagert, den Rest in der main.cpp gelassen. Der Compiler sagt, er würde die Klasse A nicht kennen. 😕
    Liegt es vielleicht an der Reihenfolge, in der er die linkt? Der Header ist aber überall eingebunden, eigentlich müsste main.cpp sagen "brauche A, haben den Rest" und header "habe A, brauche nix" ... 😕

    Bei dem Minimalbeispiel, was hier steht, klappt das Problemlos.
    Ich bin irritiert .... 😕



  • Lymogry, wenn du Dir noch kein schlaues C++ Buch besorgt hast, wäre das jetzt eine prima Gelegenheit. Deine hier gestellten Fragen, lassen sich größtenteils beantworten, wenn man die Grundlagen (separate compilation, one definition rule) drauf hat.

    Lymogry schrieb:

    Wenn ich jetzt zwei Klassen hätte und die andere Klasse B hätte Objekte von A (oder wäre eine Ableitung). Müssen diese Klassen in die selbe Datei oder in andere Headern?

    Müssen tun die gar nichts. Halte Dich einfach an die one definition rule.

    Lymogry schrieb:

    Ich hab probeweise die erste Klasse ausgelagert, den Rest in der main.cpp gelassen. Der Compiler sagt, er würde die Klasse A nicht kennen. 😕
    Liegt es vielleicht an der Reihenfolge, in der er die linkt? Der Header ist aber überall eingebunden, eigentlich müsste main.cpp sagen "brauche A, haben den Rest" und header "habe A, brauche nix" ... 😕

    Wenn der Compiler sagt, dass er Klasse A nicht kenne, dann ist der Linker noch gar nicht zum Zug gekommen. Du wirst wohl irgendwo mindestens einen Fehler gemacht haben. Vielleicht hast du ja versehentlich 2mal denselben Include-Guard für zwei Header verwendet.



  • Ich hab gute Grundlagenbücher hier: Der C++ Programmierer, Primer, Stroustrup und Meyers.... (dann sieht man schonmal das Problem vor lauter Büchern nicht mehr)
    Und eigentlich kenne ich das mit dem Linken schon durch C und dachte, ich hätts schon verstanden, denn daa hatte ich nie Probleme mit dem Linker ...
    Ich dachte, dass mein Probleme nun durch die Klassen und deren Trennung daherrühren ... 😕

    Vielleicht hast du ja versehentlich 2mal denselben Include-Guard für zwei Header verwendet.

    hmm .. aber dafür habe ich ja den Präprozessor
    #ifndef HEADER1_H
    #define HEADER1_H

    muss ich da trotzdem aufpassen, dass ich die nicht den mehrfach nehme? Was ist mit den Stdbibliotheken?

    In C hats noch nie gemeckert .... (mache ich grad was anderes ... ich weiß es nicht ... 😞 )

    Du wirst wohl irgendwo mindestens einen Fehler gemacht haben.

    *lach* ja, davon geh ich auch aus ... 😃
    Ich breche das nun alles in ein Minimalbeispiel, aber so scheine ich alles richtig zu machen ... hmm hmm ...
    Ich breche noch runter 😃



  • ajajaj ... ich habs gefunden ... der Fehler ist eigentlich schon zu peinlich, um genannt zu werden ... 😃 oh man ...:
    Weil ich erstmal die Trennung der Klassen nicht hinbekommen habe (siehe ersten Post) ich hab das Programm mit Minimalbeispiel in Header geteilt. Die Minimalbeispiele liefen dann, also zurück ins Programm kopiert. Programm lief nicht.... Nun. Eine Klasse aus dem falschen Minimalbeispiel in .cpp kopiert, aber ihre richtige Form .hpp kopiert. Der Unterschied war winzig ... Und schon meinte er, er kenne die Klassen und die Funktionen nicht mehr ....

    sorry sorry .... 👎

    PS:

    (separate compilation, one definition rule

    ja, immer separat kompilieren lassen können! 🙂
    Keine doppelten Definitionen.
    Ist mir geläufig. Danke! 🙂

    Ihr seid spitze! 👍



  • ähm ... bei einer Sache konnte ich euch nicht ganz folgen:
    Wo soll der Output jetzt rein?
    So wie es da steht, erzeugt es ein Problem: 😕

    main.cpp

    #include <iostream>
    #include <fstream>
    #include "header1.hpp"
    
    std::ofstream Output("output.txt");
    
    int main() {
        return 0;
    }
    

    header1.hpp

    #ifndef HEADER1_HPP
    #define HEADER1_HPP
    
    class Ort {
    public:
        Ort(int einX=0, int einY=0);
        int getX() const;
        int getY() const;
        void verschieben(int x, int y);
        void anzeigen() const;
    private:
        int xKoordinate;
        int yKoordinate;
    };
    
    #endif // HEADER1_HPP
    

    header1.cpp

    #include <iostream>
    #include <fstream>
    #include "header1.hpp"
    
    Ort::Ort(int einX, int einY) : xKoordinate(einX), yKoordinate(einY) {}
    int Ort::getX() const {return xKoordinate;}
    int Ort::getY() const {return yKoordinate;}
    void Ort::verschieben(int x, int y) {xKoordinate=x; yKoordinate=y;}
    void Ort::anzeigen() const {
            std::cout << "(" << getX() << "," << getY() << ") ";
            Output << "Hallo";  // Fehler: "Output wurde in diesem Gueltigkeitsbereich nicht definiert!"
      }
    

    Merci 🙂



  • Natürlich kennt der Output nicht. Woher denn?
    Wenn der Compiler Header1.cpp kompiliert, weiß der nicht, was in main.cpp steht und umgekehrt.
    Lösung: Als Parameter übergeben oder deklarieren.



  • Nathan schrieb:

    Natürlich kennt der Output nicht. Woher denn?
    Wenn der Compiler Header1.cpp kompiliert, weiß der nicht, was in main.cpp steht und umgekehrt.

    schon klar.
    Ich dachte, globale Variablen sind jetzt auch klug und gehen mit 🙂 🙂

    Lösung: Als Parameter übergeben oder deklarieren.

    Das mit der deklaration versuche ich....
    Einmal
    std::ofstream Output;
    in header.hpp
    Und
    Output.open("output.txt")
    in main.

    Compiler sagt: Multiple Definition of Output ... 😕



  • header1.hpp

    #ifndef HEADER1_HPP
    #define HEADER1_HPP
    
    #include <ostream>
    
    class Ort {
    public:
        Ort(int einX=0, int einY=0);
        int getX() const;
        int getY() const;
        void verschieben(int x, int y);
        void anzeigen(std::ostream& wo) const; // <-- z.B. hier
    private:
        int xKoordinate;
        int yKoordinate;
    };
    
    #endif // HEADER1_HPP
    


  • Dann muss ich aber std::ostream& wo in jeder Subroutine mitschleppen, nur weil ich irgendwo mal eine Ausgabe haben möchte?

    Gibt es dazu eine Alternative? Kann ich irgendwie als Stdargument vielleicht "output.txt" hinschreiben?



  • Lymogry, globale Objekte sind sowieso (sehr oft) Käse.

    Offensichtlich kennst du das, was an "separate compilation" dranhängt doch nicht. Deswegen hab ich's ja auch erwähnt oben. Wenn der Compiler xxx.cpp kompiliert, guckt der sich höchstens die Dateien an, die dort per #include eingebunden werden. Das wars. Und bevor du etwas verwenden kannst, muss der Compiler vorher irgendwo eine Deklaration gesehen haben.

    Das, was du da im Header stehen hast, ist aber keine Deklaration. Es ist eine Definition. Du hast Output also 2mal definiert. Einmal in der Übersetzungseinheit main.cpp und einmal in header.cpp (wegen #include header.hpp). Aus der Definition kann man jetzt -- wenn unbedingt erwünscht -- eine Deklaration machen: Schreib extern davor.

    Das steht sicher alles in deinen schlauen Büchern. Mussu nur mal lesen. Das spart Dir und uns Zeit. :p (wirklich! auch dir!)



  • ok, Krümelkacker ..
    es war etwas krytisch, aber ich hab dich verstanden:

    Wenn ich den stream in main.cpp außerhalb von main() deklariere, innerhalb von main() öffne und in einer anderen Datei header.cpp mit extern nochmal deklariere, dann funktioniert es! 👍

    Ich weiß, dass globale Variablen kacke sind! Und dass deswegen der Linker nicht so recht funktioniert!
    Ich nutze keine einzige globale Variable, ausser diesem Filepointer!
    Weil:
    wenn es mehrer Funktionen sind, die wachsen, sich gegenseitig enthalten usw.
    Und irgendwann in einer dieser Subroutinen soll mal eine Dateiausgabe rein. Dann will ich das genauso unkompliziert haben, wie ein std:cout. Was soll ich also in JEDER Fkt meinen Filepointer mitschleppen?
    Dann hat eine globale Variable einen guten Sinn ....

    Oder hast du eine elegantere Option?
    Ich bin durchaus belehrbar und wissbegierig! 🤡



  • Wenn du Logging haben willst (so hab ich das verstanden), nimmste std::clog.
    In der main() machste dann:
    std::clog.rdbuf(output.rdbuf());
    Und siehe da: Alles wird in output hineingeschrieben.
    Für andere Ausgaben, ist die beste Lösung den Stream zu übergeben. Das ist zwar umständlich, aber glaub mir: Es ist besser.



  • Nathan schrieb:

    Wenn du Logging haben willst (so hab ich das verstanden), nimmste std::clog.
    In der main() machste dann:
    std::clog.rdbuf(output.rdbuf());
    Und siehe da: Alles wird in output hineingeschrieben.

    DANKE Nathen!
    Den Befehl kannte ich noch nicht. Das löst aber genau mein Problem!!!! MERCI! 🙂


Anmelden zum Antworten