#includes in header auslagern / nur einmal schreiben



  • Huhu,

    ich wollte mir das Leben heute etwas einfacher machen, da ich generell immer irgendein #include vergesse. Ich hab also eine includes.h geschrieben, alle interessanten #includes und using namespace std da reingeschreben und nurnoch die includes.h included.
    Hielt ich für ne tolle Idee, klappt aber nicht. Die includes aus der include.h werden in den anderen Dateien nicht benutzt.

    Meine Frage ist jetzt, gibt es eine Möglichkeit, das anders zu machen? Oder muss ich in jeder Datei oben erstmal x Zeilen includes schreiben? Grade wenn ich eigene .h dateien habe, nervt mich das doch irgendwie ;). Aber die bsp.cpp funktioniert nunmal leider nicht ohne

    #include <iostream>
    #include <string>
    #include "bsp.h"
    using namespace std;
    

    Dabei wäre ein einfaches #include "includes.h" doch soviel schöner 😉 Vorallem, wenn die includes irgendwo genausoviele Zeilen haben wie die Datei selber wird lächerlich 😉

    Hoffe ihr könnt mir da helfen, danke im voraus.
    Malgus


  • Mod

    Malgus schrieb:

    Hielt ich für ne tolle Idee, klappt aber nicht.

    Dein Glück, denn es ist eine ganz schlechte Idee, keine gute.

    Der Fehler liegt zwar irgendwoanders, aber da das sowieso keine gute Idee ist, würde ich da nicht weiter nachforschen.

    Oder muss ich in jeder Datei oben erstmal x Zeilen includes schreiben?

    Brauchst du die denn wirklich alle? Anfänger benutzen erfahrungsgemäß fast immer viel zu viel include für ihre eigenen Header, aber viel zu wenige bei der Standardbibliothek. Benutze genau die includes, die du auch brauchst. Dabei kannst du auch benutzen, dass ein Header indirekt einen anderen einbindet, solange du selber die Kontrolle über diesen Header hast (also nicht bei Headern der Standardbibliothek, außer den paar, bei denen diesvom Standard garantiert ist). Mit dieser Technik braucht man dann meistens eher wenige includes.

    Ein using namespace hat in Headern übrigens gar nichts zu suchen. Damit hebt man den ganzen Sinn von namespaces auf, da nun jeder der deinen Header benutzt, den entsprechenden Namespace im globalen Namespace ausgeschütttet hat, ob er will oder nicht.



  • SeppJ schrieb:

    Ein using namespace hat in Headern übrigens gar nichts zu suchen. Damit hebt man den ganzen Sinn von namespaces auf, da nun jeder der deinen Header benutzt, den entsprechenden Namespace im globalen Namespace ausgeschütttet hat, ob er will oder nicht.

    Schlimmer noch: man ändert die Semantik anderer Header, die hinter einem eingebunden werden.



  • Dass die Idee nicht so dolle ist, hab ich dann auch rausgefunden, als ich mich weiter reingelesen habe. Dennoch sollte das include eig. den gesamten Text aus der .h übernehmen, oder?
    Schlechte idee oder nicht, ich würd doch gerne verstehen, warum es nicht klappt ;). Denn entweder habe ich das include falsch verstanden (was zu ganz bösen Fehlern führen könnte) oder mein compiler hat das include falsch verstanden (was zu ganz fiesen Fehlermeldungen führen würde :P).

    includes.h

    #include <string> 
    #include <iostream> 
    using namespace std;
    

    blubb.h

    #include "includes.h" 
    string test;
    

    Sollte doch theoretisch funktionieren (dass es Müll ist akzeptiere ich ^^). Zumindest dachte ich, dass der compiler daraus fogendes macht:
    blubb.h:

    #include <string> 
    #include <iostream> 
    using namespace std;
    string test;
    

    Danke schonmal für die Antworten 🙂

    P.s. Könnte mir jemand das using namespace std; erklären? Ich programmier noch nicht so lange cpp, verstehe aber gerne, was ich da eig. tuhe. (Nen guter Link würde mir auch reichen, hab das aber bisher nicht so richtig gerafft.)



  • Muss mich dringend registrieren, damit ich meine Beiträge editieren kann. Auch wenn Doppelposts hässlich machen, das "tuhe" tut mir in den Augen weh, das kann ich so nicht stehen lassen 😉 Deswegen:
    tue*
    (Und ja, grässliches Wort -> mache*) 😉



  • using namespace std;
    

    holt alles aus dem Namensraum std in den Scope, in dem die Using-Direktive steht. So kannst du danach statt std::string string und statt std::cout cout schreiben. Namensräume sind halt...das ist gar nicht so einfach in anderen Worten zu beschreiben. Es geht dabei um Namensauflösung. Wenn du schreibst

    #include <iostream>
    
    namespace hello {
      void say_it() { std::cout << "Hello, world.\n"; }
    
      void say_it_twice() {
        // say_it ist im selben Namensraum, daher hier bekannt.
        say_it();
        say_it(); 
      }
    }
    
    int main() {
      // say_it();               // Das geht nicht, weil say_it hier nicht bekannt ist.
      hello::say_it();           // Voller Name angegeben. So geht's.
    
      using hello::say_it_twice; // "Wenn du hier nach say_it_twice suchst, kuck auch in namespace hello."
      say_it_twice();            // Danach ist say_it_twice hier bekannt.
    
      using namespace hello;     // Danach sind alle Bezeichner aus namespace hello hier bekannt.
      say_it();                  // Geht.
    }
    

    Das ist die Hauptfunktionalität. Es gibt da noch ein bisschen mehr (namespace-Aliases, ADNL, verschachtelte Namensräume), aber wenn du ein vernünftiges Buch hast, kommt das alles zu gegebener Zeit dann ran. Das ist auch alles kein Hexenwerk.

    Ansonsten macht #include tatsächlich nichts anderes, als den Inhalt der eingebundenen Datei an dieser Stelle einzupasten, bevor das ganze an den Compiler geht. Der Code, so wie du ihn zeigst, sollte eigentlich kompilieren, obwohl es ohne main() natürlich danach Linkerfehler hageln wird.



  • Das mit dem namespace hab ich jetzt verstanden, danke. Gibt es einen stop using namespace Befehl? Bzw. irgendwas was ich ans Ende der .h. schreiben könnte, oder lass ich das lieber ganz aus dem header weg?

    Den Fehler beim includen hab ich auch gefunden (hatte anfangs versucht, in der include.h den test.h zu includen und in test.h die include.h, hab aber nicht herausgefunden, wie ich das so bedinge, dass keine Schleife entsteht und alles funktioniert - hab dann aber test.h in der main.cpp nicht mehr als include drin gehabt, weil das ja ursprünglich über die include.h laufen sollte (die ich heute noch lösche 😉 ).

    Ein Problem hab ich noch. Ich habe eine globale int Variable im header (multi_rotae.h) definiert. Dieser header ist nur in der zugehörigen multi_rotae.cpp und der main.cpp included.
    Fehler:

    multi_rotae.obj : error LNK2005: "int MaxID" (?MaxID@@3HA) ist bereits in main.obj definiert.

    Code:

    #include "crap.h"
    #include "multi_rotae.h"
    
    void vAufgabe1();
    
    void main(){
    
    vAufgabe1();
    
    }
    
    void vAufgabe1(){
    multi_rotae vehikel1("audo1");
    multi_rotae vehikel2;
    multi_rotae *vehikel3 = new multi_rotae;
    delete vehikel3; 
    return;
    }
    

    multi_rotae.cpp

    #include "crap.h"
    #include "multi_rotae.h"
    
    multi_rotae::multi_rotae(void) : p_sName(""), p_iID(++MaxID)
    {
    	cout << "born: " << p_sName << endl;
    	cout << "id: " << p_iID << endl;
    }
    
    multi_rotae::multi_rotae(string itself) : p_sName(itself), p_iID(++MaxID)
    {
    	cout << "born: " << p_sName << endl;
    	cout << "id: " << p_iID << endl;
    }
    
    multi_rotae::~multi_rotae(void)
    {
    	cout << "trashed: " << p_sName << endl;
    	cout << "id: " << p_iID << endl;
    }
    

    main.cpp

    #include "crap.h"
    #include "multi_rotae.h"
    
    void vAufgabe1();
    
    void main(){
    
    vAufgabe1();
    
    }
    
    void vAufgabe1(){
    multi_rotae vehikel1("audo1");
    multi_rotae vehikel2;
    multi_rotae *vehikel3 = new multi_rotae;
    delete vehikel3; 
    return;
    }
    

    und die crap.h (ich schwörs, sie kommt weg :D)

    //#ifndef crapdef
    //#define crapdef
    //#error nicht im ernst
    #include <iostream>
    #include <string>
    //#include "multi_rotae.h"
    using namespace std;
    //#endif //crapdef
    

    Die ganzen comments stammen noch aus meiner Fehlersuche, ich mach mich jetzt dran, die includes ordentlich zu verteilen, den Fehler hatte ich aber auch schon vorher. Ist alles noch nicht fertig und so, aber falls euch noch anderer Murks auffällt, dürft ihr natürlich auch darauf hinweisen ;). Verstehe nur nicht, warum die MaxID jetzt doppelt definiert ist.


  • Mod

    Malgus schrieb:

    Ein Problem hab ich noch. Ich habe eine globale int Variable im header (multi_rotae.h) definiert.

    Und so hast du eine Definition, überall wo der Header eingebunden wird, wo es doch bloß eine Definition eines Bezeichners in einem Programm geben darf (bis auf gewisse inline Sonderregeln). Definitionen im Header sind daher noch so eine Designtodsünde. Du willst vermutlich nur eine Deklaration. Oder besser gar nichts, denn Variablendeklarationen im Header bedeuten globale Variablen, die noch schlimmer sind, als alles was bisher im Thread genannt wurde.

    Der Rest deines Programms ist auch ziemlich, naja, schlecht. Was sollen die ganzen Pointer und das ganze new? Faustregel: Benutzt du als Anfänger new, machst du zu 99% was falsch. Erfahrungsgemäß stehen dahinter oft irgendwelche alten Javakenntnisse, wo man das eben so macht. In C++ aber eher nicht.



  • Alles was in vAufgabe1() steht kommt aus Aufgabe1 ;). Ich kann ja nix dafür, wenn unsere Informatikpraktikumsersteller sagen, sie wollen das so haben ;).
    Ich würd persönlich am liebsten die Pointer über Bord schmeißen, da ich auch nicht weiss, was die hier sollen. Darf ich aber nicht. Genauso wie mir vom Betreuer vorgeschlagen wurde, das using namespace std; in den header zu schreiben. (Das wird im Praktikumsskript zwar benutzt, aber nicht erklärt)

    Die MaxID brauche ich aber eig. im Header um damit durchzuzählen, wie oft ich die Klasse multi_rotae aufgerufen habe, bzw. wieviele Instanzen es davon gibt. (Ist Instanzen das richtige Wort?)

    Kann ich die hinter

    class multi_rotae
    {
    

    schieben?
    Sie muss halt im Konstruktor hochgezählt werden und bei der nächsten Erzeugung bekannt sein (d.h. public reicht, oder?)


  • Mod

    Malgus schrieb:

    Alles was in vAufgabe1() steht kommt aus Aufgabe1 ;). Ich kann ja nix dafür, wenn unsere Informatikpraktikumsersteller sagen, sie wollen das so haben ;).
    Ich würd persönlich am liebsten die Pointer über Bord schmeißen, da ich auch nicht weiss, was die hier sollen. Darf ich aber nicht. Genauso wie mir vom Betreuer vorgeschlagen wurde, das using namespace std; in den header zu schreiben. (Das wird im Praktikumsskript zwar benutzt, aber nicht erklärt)

    😡

    Du Armer. 😞

    Die MaxID brauche ich aber eig. im Header um damit durchzuzählen, wie oft ich die Klasse multi_rotae aufgerufen habe, bzw. wieviele Instanzen es davon gibt. (Ist Instanzen das richtige Wort?)

    Einen Instanzenzähler macht man normalerweise so:

    (Ungetestet)

    // foo.h (Includeguards lasse ich hier mal weg, die gehören natürlich auch noch rein):
    class foo
    {
     static unsigned int counter;
    
     public:
     foo() {++counter;}
     foo(const foo&) {++counter;}
     ~foo() {--counter;}
    };
    
    // foo.cpp:
    #include "foo.h"
    
    unsigned int foo::counter = 0;
    


  • Klingt logisch, stellt sich aber grade heraus, dass die Variable int p_MaxID heisst und in den Aufgaben steht, alles was mit p anfängt ist private oder protected.
    Aber kann man den counter echt private machen? Und was bedeutet das für die abgeleiteten Klassen (die noch kommen ..).

    Ich würd mir das alles selber anlesen, aber unser Skript ist schrecklich, ein Lehrbuch habe ich (noch) nicht und google ist zwar nett, aber leider findet man da viel mehr als man wissen möchte und meistens genau das nicht, was man sucht. Liegt vllt. daran, dass ich die Suchwörter nicht ordentlich auswähle, aber dafür bräuchte ich mehr Wissen. Ein Teufelskreis :D.

    Sieht dann so aus: (funktioniert auch^^)

    //aus der multi_rotae.h
    class multi_rotae
    {
    private:
    	std::string p_sName;
    	int p_iID;
    	double p_dMaxSpeed;
    	double p_dKMCount;
    	double p_dTimeCount;
    	double p_dCount;
    	multi_rotae(multi_rotae&);
    	static unsigned int p_uiMaxID;						//funktioniert das als private?
    	void vInitialisierung();
    
    //multi_rotae.cpp
    
    #include <string>
    
    #include "multi_rotae.h"
    
    #include <iostream>
    
    static unsigned int multi_rotae::p_uiMaxID=0;
    
    void multi_rotae::vInitialisierung(void){
    p_iuID=++p_uiMaxID;
    p_sName="";
    }
    
    multi_rotae::multi_rotae(void)
    {
    	vInitialisierung();
    	std::cout << "born: " << p_sName << std::endl;
    	std::cout << "id: " << p_iuID << std::endl;
    }
    
    multi_rotae::multi_rotae(std::string itself) : p_sName(itself)
    {
    	vInitialisierung();
    	std::cout << "born: " << p_sName << std::endl;
    	std::cout << "id: " << p_iuID << std::endl;
    }
    
    multi_rotae::~multi_rotae(void)
    {
    	std::cout << "trashed: " << p_sName << std::endl;
    	std::cout << "id: " << p_iuID << std::endl;
    }
    

    Das scheint soweit zu funktionieren. Die Id zählt hoch.

    Danke 🙂


  • Mod

    Malgus schrieb:

    Klingt logisch, stellt sich aber grade heraus, dass die Variable int p_MaxID heisst und in den Aufgaben steht, alles was mit p anfängt ist private oder protected.

    Bei mir ist sie doch auch private.

    Aber kann man den counter echt private machen? Und was bedeutet das für die abgeleiteten Klassen (die noch kommen ..).

    Die können den Counter nicht sehen und das ist auch richtig so. Denk dran, dass eine Kindklasse ein vollständiges Exemplar der Elternklasse(n) enthält, der Zähler der Elternklasse zählt also auch die Kindinstanzen mit.

    Bezüglich deines Beispiels:
    Konstruktoren benutzen! Eine Methode mit Namen Initialisierung (oder ähnlich) ist fast immer ein Designfehler. Sonst kommt es ganz schnell, dass du deine Instanzen nicht richtig zählst (oder allgemein: Die Initialisierung läuft nicht richtig), weil dir nicht garantiert ist, dass die Methode aufgerufen wird oder dass sie auch nur einmal aufgerufen wird.

    P.S.: weißt du überhaupt was std::endl bedeutet? Wenn du sagst "Zeilenumbruch", dann ist die Antwort nicht vollständig. Mach also '\n', wenn du nur Zeilenumbruch meinst.



  • Ich rufe die vInitialisierung in jedem Konstruktor auf. Sie schreibt in die Variablen "" oder 0 rein, was dann kurz danach im Konstruktor geändert wird. Klingt nicht logisch? Naja, steht so in der Aufgabe :((.

    std::endl hab ich, wie auch sonst, auch aus unserem Skript. Werd jetzt mal googeln, was das macht und ggf. \n verwenden. Ich ärger mich grade, dass diese ganzen Kleinigkeiten nirgendwo (in unserem Skript usw.) erklärt sind. (Zitat Skript:

    endl steht für neue Zeile, stattdessen kann auch \n in einem string verwendet werden.

    ).

    Ich hatte grade mal versucht einen namespace zu erstellen, um in der multi_rotae.cpp nicht immer multi_rotae:: schreiben zu müssen. Hat leider nicht so geklappt. Gibts ne andere Möglichkeit, sich das zu sparen?


  • Mod

    Malgus schrieb:

    Ich hatte grade mal versucht einen namespace zu erstellen, um in der multi_rotae.cpp nicht immer multi_rotae:: schreiben zu müssen. Hat leider nicht so geklappt. Gibts ne andere Möglichkeit, sich das zu sparen?

    Nein, außer du definierst die Methoden direkt in der Klassendefinition. Was dann aber wieder eine subtil andere Bedeutung hat, da die Methoden dann inline sind.

    endl steht für neue Zeile, stattdessen kann auch \n in einem string verwendet werden.

    Wie du vielleicht schon bemerkt hast, wird dir nur Scheiß beigebracht.



  • Malgus schrieb:

    Werd jetzt mal googeln, was das macht und ggf. \n verwenden

    Da brauchst du nicht lange googeln, da gibts mitterweile auch schon einen Thread. 😉
    http://www.c-plusplus.net/forum/308808-full?highlight=flush



  • Da ich immernoch keinen Plan habe was im namespace std so alles drin steht und mich das langsam gruselt, hab ich grade aufgehört, using namespace std zu verwenden 😉
    Sieht dann in der main.cpp so aus:

    using ::std::endl;
    using ::std::cout;
    using ::std::cin;
    using ::std::string;
    using ::std::setiosflags;
    using ::std::ios;
    using ::std::setw;
    

    😃

    Hab jetzt einige endl's durch \n ersetzt. Hab aber immer die letzten in eienr Methode so gelassen, damit zum Beispiel bei Fehlern die Ausgabe bis dahin aktualisiert ist.

    Ich verwende inzwischen beim cout ein setiosflags(ios::left)
    Macht es Sinn, das am Programmende noch zu reseten? (Zentrieren geht nicht, oder?)
    Setw() muss ich auch benutzen, das wird aber nicht erklärt. Sehe ich das richtig, dass das einfach die Feldbreite des nächsten Ausgabeelements setzt? Was passiert, wenn mein Element breiter ist? Oder wenn ich da ein \n oder ein endl reinschreibe?

    Mir scheint, die wollen nur, dass wir irgendwie die Aufgabe schaffen, aber nicht, dass wir ordentlich programmieren lernen. Deswegen muss ich jetzt anscheinend alles hier nachfragen oder googeln, was evt. irgendwelche Sachen tun könnte... Doll 😞

    Naja, hoffe ihr nehmt mir das nicht übel und beantwortet meine Fragen weiter so nett und fleissig :).


  • Mod

    Malgus schrieb:

    Macht es Sinn, das am Programmende noch zu reseten?

    Nein. Formatierung betrifft nur dein Programm.

    (Zentrieren geht nicht, oder?)

    Nicht direkt, musst du selber programmieren. Ist derzeit wahrscheinlich noch deutlich über deinem Kenntnisstand

    Setw() muss ich auch benutzen, das wird aber nicht erklärt. Sehe ich das richtig, dass das einfach die Feldbreite des nächsten Ausgabeelements setzt? Was passiert, wenn mein Element breiter ist? Oder wenn ich da ein \n oder ein endl reinschreibe?

    Am besten einfach ein bisschen experimentieren und Beispiele angucken, z.B. hier:
    http://www.cplusplus.com/reference/iostream/manipulators/



  • Hui, ich hab im Skript doch noch die Erklärung zu den namespaces gefunden. Irgendwo versteckt zwischen den Erklärungen zu Klassen. Das macht es zwar nicht sinnvoller, es sich falsch anzugewöhnen, aber naja, zumindest steht da, dass man das eig. nicht machen sollte.

    Habe jetzt aber ein anderes Problem:

    //aus der main.cpp :
    //es gibt eine globale Variable dGlobaleZeit (mit =0.0 initialisiert)
    
    for(int i=1; i<10; i++){
    	dGlobaleZeit+=i/10;
    
    	vehikel3->vAbfertigung();
    	vehikel2.vAbfertigung();
    	vehikel1.vAbfertigung();
    	vehikel3->vAusgabe();
    	vehikel2.vAusgabe();
    	vehikel1.vAusgabe();
    }
    
    //aus der multi_rotae.cpp 
    
    void multi_rotae::vAusgabe(){
    
    	cout << setw(5) << p_uiID << setw(10) << p_sName << setw(5) << ':' << setw(15) << p_dMaxSpeed << setw(15) << p_dKMCount << endl;
    
    }
    
    void multi_rotae::vAbfertigung(){
    	extern double dGlobaleZeit; //hab vorher versucht, das am Anfang dieser .cpp bekannt zu machen, Fehler bleibt aber
    	if (dGlobaleZeit-p_dTimeCount){
    		double dVergZeit=dGlobaleZeit-p_dTimeCount;
    		p_dKMCount+=dVergZeit*p_dMaxSpeed;
    		p_dTimeCount=dGlobaleZeit;
    	}
    	else {cerr << p_sName << "wurde grade zum zweiten Mal aufgerufen";}
    
    }
    

    Bei jedem Aufruf der vAbfertigung geht das Programm in den else Teil. Anscheinend ist 0.1-0 nicht true. Beide Variablen sind vom Typ double. Wenn ich dGlobaleZeit = i; setze gehts.


  • Mod

    Wenn du schon willst, dass sich jemand durch globale-Variablen-Spaghetticode wühlt, dann gib bitte ein vollständiges Programm. Siehe dritter Link in meiner Signatur.

    0.1-0 ist true, folglich wird das wohl nicht 0.1-0 sein, wo du denkst, es wäre 0.1-0. Ein Debugger hilft auch ungemein. Das wichtigste Werkzeug des Programmierers, besonders wenn man es mit einem schwer nachvollziehbarem Programm zu tun hat.

    Habe ich eigentlich schon gesagt, dass ihr nur Mist beigebracht bekommt?



  • Die Typpraefixe sind ekelhaft.


Log in to reply