Statische Variable Problem



  • DarkBug schrieb:

    Habe meinen Fehler gefunden. Die Foo-Datei darf nicht die Endung cpp haben. Sie muss die Endung h haben, sonst bekommt man die Fehlermeldung, das die Variable Bar mehrfach deklariert worden wäre.

    Kann ich nicht nachvollziehen.

    Hier mein Quellcode:

    Main.cpp

    #include <iostream>
    #include <conio.h>
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	cout << Foo::GetBar();
    
    	getch();
    	return 0;
    }
    

    Foo.h

    #ifndef __Foo__
    #define __Foo__
    
    #include <iostream>
    
    using namespace std;
    
    class Foo
    {
    	private:
    		static string Bar;
    
    	public:
    		static string GetBar()
    		{
    			return Bar;
    		}
    };
    
    string Foo::Bar = "Diese Datei funktioniert nur als .h Datei!";
    
    #endif
    

    Funktioniert hier, weil die Foo.h nur in einer .cpp eingebunden ist. Im Normalfall bindest du einen Header in mehreren Übersetzungseinheiten ein, und dann hast du mehrfache Definitionen (-> Linkerfehler).

    Nein, ich habe keine konkrete Frage dazu. Was gibt es denn alles zu kritisieren? Der wxFormBuilder setzt die Include-Guards genauso wie ich, mit doppelten Unterstrichen und auch im Header.

    Na und, dann ist das halt falsch. Du wirst auch öfter in offiziellen Hilfetexten von Borland, Microsoft usw. fehlerhafte Programme finden. Gewöhn dich dran.

    Kritik:

    Main.cpp:

    #include <iostream>
    #include <conio.h> // nicht standardkonform ... ok, falls du weißt was du tust
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv) // brauchst du die Parameter für irgendwas?
    {
    	string HelloWorld; // Kannst du später deklarieren und gleich initialisieren. 
                               // Vorn großgeschriebene Variablennamen sind auch sehr ungewöhnlich
    	Foo FooBar;
    
    	FooBar.SetBar("Hello World!");
    	HelloWorld = FooBar.GetBar();
    
    	cout << HelloWorld;
    
    	getch();
    	return 0;
    }
    

    Foo.h:

    #ifndef __Foo__  // wie gesagt, doppelte Unterstriche sind reserviert
    #define __Foo__
    
    #include <iostream>
    
    using namespace std; // niemals im Header Namensräume öffnen
    
    class Foo
    {
    	private:
    		string m_Bar;
    
    	public:
    		string GetBar(); // sollte const deklariert werden
    		void SetBar(string par_Bar); // Kopien sind ineffizient, Argument lieber per const-Referenz übergeben. Die Benennung mit "par_" ist auch sehr ungewöhnlich
    };
    
    #endif
    

    Foo.cpp:

    #include "Foo.h"
    // hier dann das 'using namespace std;'
    string Foo::GetBar()
    {
    	return m_Bar;
    }
    
    void Foo::SetBar(string par_Bar)
    {
    	m_Bar = par_Bar;
    }
    

    Oder orientier dich an dem Beispiel von Swordfish.

    PS: Das war jetzt rein eine Kritik an der Implementierung. Das Design ist nochmal eine andere Frage, die man ohne zu wissen, wozu die Klasse da ist, gar nicht beantworten kann. Es sieht aber so aus, als ob die Klasse keinen Sinn hat und deshalb das beste Design ist, sie zu löschen.



  • Bashar schrieb:

    DarkBug schrieb:

    Habe meinen Fehler gefunden. Die Foo-Datei darf nicht die Endung cpp haben. Sie muss die Endung h haben, sonst bekommt man die Fehlermeldung, das die Variable Bar mehrfach deklariert worden wäre.

    Kann ich nicht nachvollziehen.

    Hier mein Quellcode:

    Main.cpp

    #include <iostream>
    #include <conio.h>
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	cout << Foo::GetBar();
    
    	getch();
    	return 0;
    }
    

    Foo.h

    #ifndef __Foo__
    #define __Foo__
    
    #include <iostream>
    
    using namespace std;
    
    class Foo
    {
    	private:
    		static string Bar;
    
    	public:
    		static string GetBar()
    		{
    			return Bar;
    		}
    };
    
    string Foo::Bar = "Diese Datei funktioniert nur als .h Datei!";
    
    #endif
    

    Funktioniert hier, weil die Foo.h nur in einer .cpp eingebunden ist. Im Normalfall bindest du einen Header in mehreren Übersetzungseinheiten ein, und dann hast du mehrfache Definitionen (-> Linkerfehler).

    Habs ausprobiert. Hast recht. Aber dennoch verstehe ich nicht warum das so ist. Der Guard __Foo__ sorgt doch dafür, das es nur einmal definiert wird.

    Wenn ich den Namensraum nicht im Header anspreche kennt der Kompiler std::string nicht, weil er string ließt.

    Der Rest ist ok.



  • DarkBug schrieb:

    Habs ausprobiert. Hast recht. Aber dennoch verstehe ich nicht warum das so ist. Der Guard __Foo__ sorgt doch dafür, das es nur einmal definiert wird.

    Nein, der Include-Guard sorgt dafür, dass die Definition in jeder Übersetzungseinheit (= cpp-File) höchstens einmal vorkommt. Beim Zusammenlinken der übersetzten Objektfiles kotzt dann der Linker, falls sie in mehreren Übersetzungseinheiten vorkommt.
    Wenn man einmal verstanden hat, wie die Übersetzung abläuft, ist das eigentlich ganz klar.

    Wenn ich den Namensraum nicht im Header anspreche kennt der Kompiler std::string nicht, weil er string ließt.

    Wenn der Compiler std::string nicht kennt, musst du <string> einbinden. Mit dem using namespace hat das überhaupt nichts zu tun, das ist dafür verantwortlich, dass du string statt std::string schreiben kannst.

    Wenn man davon ausgeht, dass Namespaces einen Sinn haben, dann will man von Fall zu Fall entscheiden, ob man einen Namespace lieber öffnet oder, um Namenskollisionen zu vermeiden, lieber die einzelnen Elemente explizit mit ihrem Namespace versieht. Wenn du in einem Header den Namespace öffnest, nimmmst du dem Benutzer des Headers diese Entscheidung ab, er hat dann keine Chance, das rückgängig zu machen. Deshalb die Grundregel: In Headern niemals using namespace verwenden.



  • Bashar schrieb:

    DarkBug schrieb:

    Habs ausprobiert. Hast recht. Aber dennoch verstehe ich nicht warum das so ist. Der Guard __Foo__ sorgt doch dafür, das es nur einmal definiert wird.

    Nein, der Include-Guard sorgt dafür, dass die Definition in jeder Übersetzungseinheit (= cpp-File) höchstens einmal vorkommt. Beim Zusammenlinken der übersetzten Objektfiles kotzt dann der Linker, falls sie in mehreren Übersetzungseinheiten vorkommt.
    Wenn man einmal verstanden hat, wie die Übersetzung abläuft, ist das eigentlich ganz klar.

    Achso, das heißt Deklarationen dürfen so oft vor kommen wie sie wollen, Definitionen aber nur einmal?

    Beispiel Klasse Foo:

    Statische Variable Deklaration = static string Bar;
    Statische Variable Definition = string Foo::Bar = "Ok";

    Statische Methoden Deklaration = static string GetBar();
    Statische Methoden Definition = string Foo::GetBar() { return Bar; }

    Bashar schrieb:

    DarkBug schrieb:

    Wenn ich den Namensraum nicht im Header anspreche kennt der Kompiler std::string nicht, weil er string ließt.

    Wenn der Compiler std::string nicht kennt, musst du <string> einbinden. Mit dem using namespace hat das überhaupt nichts zu tun, das ist dafür verantwortlich, dass du string statt std::string schreiben kannst.

    Wenn man davon ausgeht, dass Namespaces einen Sinn haben, dann will man von Fall zu Fall entscheiden, ob man einen Namespace lieber öffnet oder, um Namenskollisionen zu vermeiden, lieber die einzelnen Elemente explizit mit ihrem Namespace versieht. Wenn du in einem Header den Namespace öffnest, nimmmst du dem Benutzer des Headers diese Entscheidung ab, er hat dann keine Chance, das rückgängig zu machen. Deshalb die Grundregel: In Headern niemals using namespace verwenden.

    Interessant. Heißt also in Header-Dateien die einzelnen Elemente immer explizit mit ihrem Namespace versehen.

    std::cout
    std::string

    etc.



  • Genau.



  • DarkBug schrieb:

    Achso, das heißt Deklarationen dürfen so oft vor kommen wie sie wollen, Definitionen aber nur einmal?

    Hmmm, dies scheint doch nur auf Variablen zuzutreffen. Jedenfalls funktioniert folgender Code:

    Foo.h

    #ifndef __Foo__
    #define __Foo__
    
    #include <iostream>
    
    class Foo
    {
    	private:
    		static std::string Bar;
    		static std::string FooBar;
    
    	public:
    		static std::string GetBar()
    		{
    			return Bar;
    		}
    
    	public:
    		static std::string GetFooBar()
    		{
    			return FooBar;
    		}
    };
    
    #endif
    

    Foo.cpp

    #include "Foo.h"
    
    using namespace std;
    
    string Foo::Bar = "Hello";
    string Foo::FooBar = "World";
    

    Main.cpp

    #include <iostream>
    #include <conio.h>
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	cout << Foo::GetBar() + " " + Foo::GetFooBar();
    
    	getch();
    	return 0;
    }
    

    Test.cpp

    #include "Foo.h"
    


  • OK, das war ein bisschen vereinfacht. Es gibt zu dieser "One-Definition Rule" (ODR) ein paar Ausnahmen. Definitionen von inline-Funktionen und Funktionstemplates dürfen nämlich mehrmals vorkommen. Memberfunktionen, die innerhalb des Klassenkörpers definiert werden, sind automatisch inline.



  • Achso. Ok. Es ist aber nicht möglich, die statischen Variablen aus der Foo.cpp inline mit in die Foo.h einzubinden, so das man sich die Foo.cpp sparen kann, oder?



  • DarkBug schrieb:

    Es ist aber nicht möglich, die statischen Variablen aus der Foo.cpp inline mit in die Foo.h einzubinden, so das man sich die Foo.cpp sparen kann, oder?

    Nein, das ist nicht möglich.

    (Wenn ich so drüber nachdenke, ist das eigentlich relativ unlogisch, dass man das so geregelt hat. Der Linker muss sowieso doppelte Vorkommen von inline-Funktionen (falls der Compiler sie generiert hat, was er nicht immer vermeiden kann) entfernen, das könnte er eigentlich auch für Variablen tun. inline -Variablen 😉 )



  • Schade, da kann ich dir nur zustimmen. Besten dank für deine Hilfe. Mein Problem ist damit behoben.


Log in to reply