C++ static variable in Konstruktor setzen

Tockra

Lt. Commander
Registriert
Dez. 2008
Beiträge
1.058
Hallo Leute,

ich möchte eigentlich was komplett simples umsetzen, allerdings meckert spätestens beim compilen mein Compiler. Ich würde gerne eine statische private Member Variable in einer Klasse im Konstruktor der Klasse setzen.
Ich habe es wie folgt probiert, dann kommen allerdings die folgenden Errrors:

Code:
BroadcastListener::BroadcastListener(unsigned short control_port) {
	BroadcastListener::broadcast_port = PORT;

}

Code:
Schweregrad	Beschreibung	Projekt	Datei	Zeile
Warnung	warning LNK4272: Bibliothekcomputertyp "UNKNOWN" steht in Konflikt mit dem Zielcomputertyp "X86"	Netzwerkprogramm	OLDNAMES.lib	1
Fehler	error LNK2001: Nicht aufgelöstes externes Symbol ""private: static unsigned short BroadcastListener::broadcast_port" (?broadcast_port@BroadcastListener@@0GA)".	Netzwerkprogramm	BroadcastListener.obj	
Fehler	error LNK1120: 1 nicht aufgelöste Externe	Netzwerkprogramm	Netzwerkprogramm.exe	1
 
sicherlich geht das..

Der HEader
Code:
#pragma once
class TestClass
{
private:
    static int my_static;
public:
    TestClass(void);
    ~TestClass(void);
};
Der code selbst:
Code:
#include "TestClass.h"

int TestClass::my_static = 123;
TestClass::TestClass(void)
{
    my_static = 456;
}
Habe paar sachen weggelassen
wichtig ist, das du die Static member variable initialisieren musst BEVOR du ein Object anlegst. Das passiert in der Zeile 3: int TestClass::my_static = 123;
 
Überleg dir aber vorher noch mal genau, ob die statische Variable wirklich benötigt wird, oder ob man das nicht auch anders lösen kann. Statische Variablen bringen einige Einschränkungen bezüglich der Benutzbarkeit einer Klasse mit sich. Zum Beispiel ist deine Klasse damit schon mal automatisch nicht mehr thread-sicher. Soll heißen, deine Klasse kann nun nicht mehr in mehreren Threads instanziiert werden ... es sei denn, du baust noch einen Schutzmechanismus ein, der das gewährleistet (z.B. einen Mutex).
 
Zuletzt bearbeitet: (Rächtschreipnug)
Hallo,
antred hat recht. Es macht keinen Sinn, eine statische Variable in einem Konstruktor zu initialisieren...

Konstruktor --> Instanzspezifisch
static --> Instanzunabhängig

Greetz
hroessler

@antred: Gerade statische Member sind threadsafe!
 
hroessler schrieb:
@antred: Gerade statische Member sind threadsafe!

Ich wundere mich, wie du diese Aussage begründen möchtest. Eine Klasse, die keine statischen Variablen hat, kann ich problemlos in mehreren Threads nutzen (so lange sonst keine Daten per Referenz / Pointer über Threads hinweg geshared werden *1). Bei einer Klasse mit statischen Variablen ist dies nicht der Fall. Instanziiert man die Beispiel-Klasse vom OP in mehreren Threads, hast du allein durch das nichtsynchronisierte Beschreiben des statischen Members im Konstruktor schon undefiniertes Verhalten produziert. Vielleicht noch nicht durch das Beschreiben, aber ganz sicher später bei lesenden Zugriffen.

P.S. *1: ... und so lange jeder Thread nur mit den Instanzen arbeitet, die er selbst erstellt hat
 
Zuletzt bearbeitet:
@antred: Ein statisches Element existiert nur einmal im gesamten Programm. Wo man aufpassen muss ist beim Initialisieren der Variable. Dies ist per default natürlich nicht threadsafe.

Vorausgesetzt, die Trennung wäre wirklich sooo sauber, dann hättest du natürlich wieder recht :)

Greetz
hroessler
 
hroessler schrieb:
@antred: Ein statisches Element existiert nur einmal im gesamten Programm.

Ja, schon. Und? Das Thema der Threadsicherheit besteht ja überhaupt immer nur dann, wenn aus mehreren Threads ein Zugriff auf __ein__ geshared'tes (sorry für diese Eindeutschung) Datum erfolgt.
 
hroessler schrieb:
@antred: Ein statisches Element existiert nur einmal im gesamten Programm. Wo man aufpassen muss ist beim Initialisieren der Variable. Dies ist per default natürlich nicht threadsafe.

Hat sich das mit der thread safety nicht in C++11 geändert?
 
Alles klar, mein Fehler war wohl, dass ich den Typ der Variablen nicht bei der Zuweisung angegeben habe, da dies für mich etwas unüblich schien, da der Typ ja bereits in der Header Datei genannt war.


Wirklich nötig ist die statische Variable nicht wirklich, zumal es sowieso nur eine Instanz dieser Klasse gibt. Es geht eigentlich darum, dass in dieser Variablen ein Port für eine UDP bzw. TCP Anwendung gespeichert wird und andere Klassen diesen Port mitgeteilt bekommen sollen, ohne dass diese Klassen eine Instanz der entsprechenden Klasse mit dem statischen Attribut haben.
 
hdmaster schrieb:
Hat sich das mit der thread safety nicht in C++11 geändert?

Wieso sollte es? Das Motto von C++ war schon immer "You don't pay for what you don't use.", und Threadsynchronisation bringt immer einen gewissen Overhead mit sich. Warum sollte C++ auf Verdacht automatisch für alles mögliche eine Threadsynchronisation mit ins Programm reinwurschteln, wenn der Benutzer das in 99% aller Fälle gar nicht benötigt?
Ergänzung ()

Tockra schrieb:
Wirklich nötig ist die statische Variable nicht wirklich, zumal es sowieso nur eine Instanz dieser Klasse gibt. Es geht eigentlich darum, dass in dieser Variablen ein Port für eine UDP bzw. TCP Anwendung gespeichert wird und andere Klassen diesen Port mitgeteilt bekommen sollen, ohne dass diese Klassen eine Instanz der entsprechenden Klasse mit dem statischen Attribut haben.

Ich würde nach wie vor davon abraten. Was spricht dagegen, jeder Instanz bei ihrer Erstellung diese Information einfach per Konstruktor mitzugeben?
 
hdmaster schrieb:
Hat sich das mit der thread safety nicht in C++11 geändert?

Ja es gibt einige Erweiterungen dafür,
Es wurden aber eigentlich nur libs standardisiert und nix neues erfunden
d.h. du bist noch immer selbst zuständig für alles, hast aber den Vorteil das diverse Thread/Synchronisationssachen jetzt Sprachstandard sind anstatt externe Libs.

(gibt aber auch wirklich Spracherweiterungen wie z.B. atomic, aber für Details am besten selbst nachlesen :) )

@Tockra
Ergänzung ()

Alternative dazu ist, das die Klasse rein statisch ist
wenn du es sowieso nicht weiterverwendest/vererbst bzw. nur einmal benutzt, dann kann das die richtige Lösung sein.
Noch eine Alternative ist eine Singelton Klasse/Object zu machen.

Wobei singelton eher ein Indiz sind, das in der Struktur deines Programms etwas faul ist. (Kann aber durchaus sinnvoll sein, vor allem wenn bei Bestehenden Projekten der Änderungsaufwand gigantisch wird)
 
Zuletzt bearbeitet:
Ich meinte ganz speziell das Initialisieren von statischen Variablen. Das ist seit C++11 tatsächlich thread safe und wird von den großen Compilern auch schon länger unterstützt. Nachzulesen u.a. im Draft in Section 6.7.4 :)
 
Tockra schrieb:
Wozu sollte ich die gesamte Klasse statisch bzw. zum Singleton machen. Ich habe gelernt man macht das nur, wenn man wirklich Gefahr laufen könnte dass 2 Instanzen erstellt werden oder so.

Nicht mal dann sind singletons wirklich eine gute Idee. Wenn man nur eine Instanz braucht, erstellt man eben nur eine.
 
hdmaster schrieb:
Ich meinte ganz speziell das Initialisieren von statischen Variablen. Das ist seit C++11 tatsächlich thread safe und wird von den großen Compilern auch schon länger unterstützt. Nachzulesen u.a. im Draft in Section 6.7.4 :)
Da bringst du statisch Klassenvariablen und lokale statische variablen durcheinander.

Statische Klassenvariablen sind eigentlich nichts anderes als globale Variablen, nur dass sie im Namensraum einer Klasse liegen. Als solche werden sie initialisiert bevor der Programmfluss main() erreicht, also auch noch lange bevor es überhaupt mehrere Threads geben kann. Genauso wie bei normalen globalen Variablen führt der unsynchronisierte Zugriff aus mehreren Threads aber zu einem data race und somit zu Undefined Behavior. Das würde in Tockra's Beispiel passieren, wenn zwei BroadcastListener in zwei separaten Threads erstellt würden.

Worauf du dich beziehst sind lokale statische variablen also sowas:
Code:
int foo() {
    static int bar = 1;  
    return bar; 
}
Lokale statisch variablen werden initialisiert, wenn der Programmfluss zum ersten mal am Ort ihrer Definition vorbeikommt (also hier, wenn zum ersten mal foo aufgerufen wird). Was c++11 garantiert ist dass diese Initialisierung nicht zu einem Datarace führt, was insbesondere für singletons sehr wichtig sein kann. Nachfolgende Zugriffe müssen aber immer noch synchronisiert werden.
Das hier:
Code:
int foo() {
    static int bar = 1;  
    bar++
    return bar; 
}
führt also immer noch zu einem data race, wenn foo() aus verschiedenen Threads aufgerufen wird.
 
Miuwa schrieb:
Da bringst du statisch Klassenvariablen und lokale statische variablen durcheinander.

Statische Klassenvariablen sind eigentlich nichts anderes als globale Variablen, nur dass sie im Namensraum einer Klasse liegen. Als solche werden sie initialisiert bevor der Programmfluss main() erreicht, also auch noch lange bevor es überhaupt mehrere Threads geben kann. Genauso wie bei normalen globalen Variablen führt der unsynchronisierte Zugriff aus mehreren Threads aber zu einem data race und somit zu Undefined Behavior. Das würde in Tockra's Beispiel passieren, wenn zwei BroadcastListener in zwei separaten Threads erstellt würden.

Das ist IMHO noch etwas unklar

Die Initalisierung von statischen Variablen ist Threadsafe (unter der Annahme der compiler baut keinen Mist)
Egal ob Klassen statisch oder Funktion/Methoden Statisch
Natürlich kannst du jetzt einige perverse sachen machen (statische Threads usw...) die diesen _Schutz_ umgehen aber in erster Näherung funktioniert es schon.

Der spätere Zugriff ist natürlich nicht mehr Threadsafe, außer du kümmerst dich explizit darum. (Wie in deinem Beispiel auch gezeigt.)
 
Das kommt jetzt darauf an, wie du Threadsafe definierst. Richtig ist, dass weder die initialisierung von statischen Klassen variablen noch von lokalen statischen variablen zu einem Datarace führen kann. Im Falle lokaler Variablen liegt das aber daran, dass der Compiler extra code produziert um diese gegen Mehrfachinitialisierung zu schützen, während es bei Klassenvariablen schlicht weg nur einen Thread geben kann, der den Initialisierungscode ausführt.

Tockra schrieb:
Alles klar, mein Fehler war wohl, dass ich den Typ der Variablen nicht bei der Zuweisung angegeben habe, da dies für mich etwas unüblich schien, da der Typ ja bereits in der Header Datei genannt war.

Das sollte bei der Zuweisung im Konstruktor eigentlich nicht nötig sein (bzw. ich wusste garnicht, dass das möglich ist) hast du vielleicht vergessen - so wie the_nobs - deine statische Variable noch mal außerhalb der Klasse zu definieren?
 
Hier soll doch eh nur der Port in dieser statischen Variable gespeichert werden, also von überall sonst nur lesend zugegriffen werden. Deshalb kann es ja eigentlich keine Race Conditions geben.
 
daemon777 schrieb:
Hier soll doch eh nur der Port in dieser statischen Variable gespeichert werden, also von überall sonst nur lesend zugegriffen werden. Deshalb kann es ja eigentlich keine Race Conditions geben.

Klar kann es das. Hast du mindestens einen Schreiber und mindestens einen Leser aus verschiedenen Threads, kann es Race-Conditions geben. Und das ist in dem Beispiel vom OP der Fall.

EDIT: Ok, der OP hat in seinem Beispiel bis jetzt nichts von Threads erwähnt, aber es geht ja hier um die prinzipielle Benutzbarkeit seiner Klasse in einer Umgebung mit mehreren Threads.
 
Zuletzt bearbeitet:
Zurück
Oben