Statische Variable Problem



  • Hallo,

    habe ein Änfängerproblem mit einer statischen Variable. Hier mein Code:

    #include <iostream>
    #include <conio.h>
    
    using namespace std;
    
    class Test
    {
    	private:
    		static string TestVar;
    
    	public:
    		Test();
    		static string GetTestVar();
    }
    
    Test::Test()
    {
    	TestVar = "Hallo";
    }
    
    static string Test::GetTestVar()
    {
    	return TestVar;
    }
    
    int main()
    {
    	cout << Test::GetTestVar();
    	getch();
    	return 0;
    }
    

    Der Kompiler sagt:
    16: Angabe des Rückgabetyps für Konstruktor ist ungültig
    21: Elementfunktion »static std::string Test::GetTestVar()« kann nicht deklariert werden, statische Bindung zu haben [-fpermissive]

    Mit freundlichen Grüßen,
    DarkBug



  • DarkBug schrieb:

    #include <iostream>
    #include <conio.h>
    
    using namespace std;
    
    class Test
    {
    	private:
    		static string TestVar;
    
    	public:
    		Test();
    		static string GetTestVar();
    }  // <- Fehlermeldung 1: Semikolon fehlt
    
    Test::Test()
    {
    	TestVar = "Hallo";
    }
    
    static string Test::GetTestVar() // Fehlermeldung 2: Das static gehört hier nicht hin
    {
    	return TestVar;
    }
    
    int main()
    {
    	cout << Test::GetTestVar();
    	getch();
    	return 0;
    }
    

    BTW ist das das falsche Forum, "DOS und Win32-Konsole" ist, wie der Name schon sagt, für Fragen speziell zu DOS- und Win32-Konsolenprogrammen gedacht.



  • Danke für deine Antwort. Jetzt sagt der Kompiler:

    18: undefined reference to Test::TestVar' 23: undefined reference toTest::TestVar'

    P.S.: Sorry, müsste ein Moderator dann verschieben.



  • Dir fehlt noch eine Definition von TestVar:

    // außerhalb der Klasse und in der cpp-Datei
    string Test::TestVar;
    


  • Danke, es kompiliert. Ich habe Variablen bisher immer nur in den Header geschrieben. Ist doch bei nicht statischen Variablen richtig? Nur statische Variablen müssen auch in die cpp geschrieben werden, oder?

    Es wird nicht wie gewünscht "Hallo" ausgegeben. Warum wird der Konstruktor ignoriert?



  • DarkBug schrieb:

    Ich habe Variablen bisher immer nur in den Header geschrieben. Ist doch bei nicht statischen Variablen richtig?

    Nein, Variablendefinitionen haben in Headerdateien nichts verloren, das führt zu Problemen, sobald so ein Header von mehr als einer .cpp-Datei eingebunden wird.

    DarkBug schrieb:

    Warum wird der Konstruktor ignoriert?

    Er wird nicht ignoriert. Er wird nie aufgerufen, weil du kein Test-Objekt erstellst.



  • DarkBug schrieb:

    Es wird nicht wie gewünscht "Hallo" ausgegeben. Warum wird der Konstruktor ignoriert?

    Warum sollte er denn deiner Meinung nach aufgerufen werden?



  • Ups, ich habe kein static vor den Konstruktor gesetzt. Habs gerade ausprobiert. Es gibt anscheinend keine statischen Konstruktoren in C++, oder? Ist das erste Mal, das ich static in C++ verwenden möchte. Habe in C# schon öfters statische Konstruktoren benutzt.

    @MFK: Ist das folgende Klassendesign nicht korrekt? So habe ich es bisher immer gemacht:

    Main.cpp:

    #include <iostream>
    #include <conio.h>
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	string HelloWorld;
    	Foo FooBar;
    
    	FooBar.SetBar("Hello World!");
    	HelloWorld = FooBar.GetBar();
    
    	cout << HelloWorld;
    
    	getch();
    	return 0;
    }
    

    Foo.h:

    #ifndef __Foo__
    #define __Foo__
    
    #include <iostream>
    
    using namespace std;
    
    class Foo
    {
    	private:
    		string m_Bar;
    
    	public:
    		string GetBar();
    		void SetBar(string par_Bar);
    };
    
    #endif
    

    Foo.cpp:

    #include "Foo.h"
    
    string Foo::GetBar()
    {
    	return m_Bar;
    }
    
    void Foo::SetBar(string par_Bar)
    {
    	m_Bar = par_Bar;
    }
    


  • // edit: Zuuuu spät.

    @OP: Wie wär's mit einer Datei als Beispiel fürs Forum!?

    #include <string>
    #include <iostream>
    
    class Foo
    {
        private:
            std::string m_Bar;
    
        public:
            std::string GetBar() const;
            void SetBar( const std::string &par_Bar);
    }; 
    
    std::string Foo::GetBar() const
    {
        return m_Bar;
    }
    
    void Foo::SetBar( const std::string &par_Bar )
    {
        m_Bar = par_Bar;
    } 
    
    int main()
    {
        Foo FooBar;
        FooBar.SetBar("Hello World!");
        std::cout << FooBar.GetBar();
    }
    


    Ist es möglich statische Variablen zu verwenden, wenn man die Methodenimplementation bereits in der Klassendeklaration vorgenommen hat?

    DarkBug schrieb:

    Ist das folgende Klassendesign nicht korrekt? So habe ich es bisher immer gemacht:

    Main.cpp:

    #include <iostream>
    #include <conio.h>
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	string HelloWorld;
    	Foo FooBar;
    
    	FooBar.SetBar("Hello World!");
    	HelloWorld = FooBar.GetBar();
    
    	cout << HelloWorld;
    
    	getch();
    	return 0;
    }
    

    Foo.h:

    #ifndef __Foo__
    #define __Foo__
    
    #include <iostream>
    
    using namespace std;
    
    class Foo
    {
    	private:
    		string m_Bar;
    
    	public:
    		string GetBar();
    		void SetBar(string par_Bar);
    };
    
    #endif
    

    Foo.cpp:

    #include "Foo.h"
    
    string Foo::GetBar()
    {
    	return m_Bar;
    }
    
    void Foo::SetBar(string par_Bar)
    {
    	m_Bar = par_Bar;
    }
    


  • DarkBug schrieb:

    Ist es möglich statische Variablen zu verwenden, wenn man die Methodenimplementation bereits in der Klassendeklaration vorgenommen hat?

    Ja.

    Das ändert überhaupt nichts. Im Extremfall musst du ein cpp-File anlegen, das nur diese eine Variablendefinition enthält.

    DarkBug schrieb:

    Ist das folgende Klassendesign nicht korrekt? So habe ich es bisher immer gemacht:

    Hast du eine konkrete Frage dazu? Man kann daran eine ziemliche Menge kritisieren und verbessern, aber wirklich falsch sind auf den ersten Blick nur die doppelten Unterstriche bei den Include-Guards: Der Bezeichner __Foo__ ist für die Implementation reserviert. Du darfst (unter anderem) keine Namen, die doppelte Unterstriche enthalten, deklarieren.



    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.

    Warum ist das so?

    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
    

    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.

    DarkBug schrieb:

    Main.cpp:

    #include <iostream>
    #include <conio.h>
    
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	string HelloWorld;
    	Foo FooBar;
    
    	FooBar.SetBar("Hello World!");
    	HelloWorld = FooBar.GetBar();
    
    	cout << HelloWorld;
    
    	getch();
    	return 0;
    }
    

    Foo.h:

    #ifndef __Foo__
    #define __Foo__
    
    #include <iostream>
    
    using namespace std;
    
    class Foo
    {
    	private:
    		string m_Bar;
    
    	public:
    		string GetBar();
    		void SetBar(string par_Bar);
    };
    
    #endif
    

    Foo.cpp:

    #include "Foo.h"
    
    string Foo::GetBar()
    {
    	return m_Bar;
    }
    
    void Foo::SetBar(string par_Bar)
    {
    	m_Bar = par_Bar;
    }
    


  • 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 😉 )


Anmelden zum Antworten