Automatische Initialisierung einer Tabelle
-
Hallo zusammen,
ich wollte letztens Tabellen, die von mehreren Instanzen einer Klasse gemeinsam genutzt werden, automatisch initialisieren lassen. Ohne also ein einmaliges
init( )aufrufen zu müssen, was dann durch ein Flag oder so vor mehrmaligem Aufruf geschützt wird.Dabei bin ich auf die Idee gekommen, dass die Klasse ein statisches Member (Tabellen-Objekt) hat, dessen Constructor die Initialisierung der eigentlichen Map o.ä. durchführt.
Als Beispiel hier eine LED-Klasse, der man einen Sequenzcode vorgeben kann (also an, aus, blinken usw.). Mit
getSequence( )erhält man über die statische (und private) Sequenztabelle dann die eigentliche Sequenz (also das Binkmuster) jedes LED-Objekts. Hinweis: Es ist Absicht, dass die LED-Objekte jeweils ihren Sequenz-Code speichern und nicht die Sequenz selbst. Mir geht es primär ja auch um das Prinzip selbst. Es sind hier auch der Einfachheit halber keine Bereichsprüfungen o.ä. dabei.Mich würde interessieren, was Ihr davon haltet (funktionieren tut es) oder ob ich was Wichtiges vergessen hab. Oder wie man etwas Ähnliches noch anders hinkriegt.
Led.h:
#include <map> class Led { public: typedef enum { OFF, BLINK, FLASH, ON } seqCodes_e; Led( ) : mySequence( OFF ) { } void setSequenceCode( seqCodes_e code_ ) { mySequence = code_; } int getSequence( ) const { return seqTable( mySequence ); } private: class SeqTable { public: SeqTable( ); int operator( )( seqCodes_e code_ ) { return sequenceOf[ code_ ]; } private: std::map<seqCodes_e, int> sequenceOf; }; static SeqTable seqTable; seqCodes_e mySequence; };Led.cpp:
#include "Led.h" Led::SeqTable Led::seqTable; // Der Constructor, der die Initialisierung durchführt Led::SeqTable::SeqTable( ) { sequenceOf[ OFF ] = 0x00; // 00000000 bin sequenceOf[ BLINK ] = 0xAA; // 10101010 bin sequenceOf[ FLASH ] = 0x80; // 10000000 bin sequenceOf[ ON ] = 0xFF; // 11111111 bin }main:
Led redLed; Led greenLed; redLed.setSequenceCode( Led::BLINK ); greenLed.setSequenceCode( Led::ON ); std::cout << "Red: " << std::hex << redLed.getSequence( ) << std::endl; std::cout << "Green: " << std::hex << greenLed.getSequence( ) << std::endl;Ausgabe:
Red: aa Green: ff~Edit: Unterscheidung [c]setSequenceCode( )[/c] / [c]getSequence( )[/c]~
-
Das kann man machen, aber:
Ich sehe den Nutzen im konkreten Fall nicht, weiltypedef enum { OFF = 0x00, BLINK = 0xaa, FLASH = 0x80, ON = 0xff } seqCodes_e;ja schon alles macht was Du brauchst.
-
gastgast schrieb:
ja schon alles macht was Du brauchst.
In dem konkreten Fall geb ich Dir absolut recht, ich verwende die Technik jedoch auch für Tabellen, die strings als Keys oder Funktionspointer als Values haben (wie gesagt, die Led-Klasse ist nur ein Demo.).
Meine Überlegungen sind eher in der Art, ob die Konstruktion / Destruktion vom Timing her sicher ist (die Tabelle an sich ist ja ein Singleton, nur eben in einer sehr vereinfachten Ausführung). Ich gehe einfach davon aus, dass der Constructor aufgerufen wird, bevor der erste Zugriff auf die Tabelle erfolgt, bin mir aber nicht 100% sicher, ob das jeder Compiler auch so sieht.
Aber wie gesagt, evtl. gibt es noch andere elegantere Wege, um sowas umzusetzen.
-
static class Member werden vor der dynamischen Konstruktion initialisiert. Von daher ist das sicher. Was aber passiert, wenn man im Code ein
static Led blueLED;hat, ist nicht definiert, weil es keine definierte Reihenfolge für die Initialisierung von statics gibt. Solange aber im Konstruktor von Led nicht auf das static zugegriffen wird, bist Du da auf der sicheren Seite.s. auch : http://stackoverflow.com/questions/1079623/what-is-the-lifetime-of-class-static-variables-in-c
-
OK, vielen Dank, v.a. für den stackoverflow-Artikel.
Ich habe gerade festgestellt, dass der Constructor noch vor main( ) aufgerufen wird, und sogar, wenn überhaupt kein LED-Objekt erzeugt wird (also nur durch das #include "Led.h"). Das ist interessant.
Solange die Tabelle nicht groß ist, sollte das kein Problem sein.
Eine sauberere Lösung wäre demnach vermutlich ein echtes Singleton, was nur bei Bedarf "anspringt".
-
Ich würde maximal die Initialisierung (Einfügen der Werte in die map) verzögern, nicht das Anlegen der std::map selbst.
boost::call_onceist eine gute Möglichkeit das ganze thread-safe zu machen.
-
-> construct on first use idiom
int getSequence( ) const { static SeqTable seqTable; return seqTable( mySequence ); }
-
brotbernd schrieb:
-> construct on first use idiom
int getSequence( ) const { static SeqTable seqTable; return seqTable( mySequence ); }* Falsche Syntax
->int getSequence() const { static SeqTable seqTable(mySequence); return seqTable; }* Nicht thread-safe mit MSVC.
* Nicht thread-safe mit C++03.
* OK mit GCC.
-
hustbaer schrieb:
brotbernd schrieb:
-> construct on first use idiom
int getSequence( ) const { static SeqTable seqTable; return seqTable( mySequence ); }* Falsche Syntax
->int getSequence() const { static SeqTable seqTable(mySequence); return seqTable; }* Nicht thread-safe mit MSVC.
* Nicht thread-safe mit C++03.
* OK mit GCC.Also einer von uns beiden guckt schief. SeqTabl hat einen ()operator, keine int Konvertierung und auch keinen solchen Ctor.
-
Ja, ICH gucke schief

Sorry, hast natürlich Recht.Thread-safety ist aber so wie beschrieben.
-
Dazu eine Frage:
- nicht thread-safe vermutlich weil ein zweiter thread getSequence aufrufen könnte, während das Objekt noch konstruiert wird (aufgrund eines Aufrufs von getsequence durch ersten thread)?Inwiefern ist das für G++ in Ordnung? Hat er diesbezüglich erweiterete Sicherheitsmechanismen?
-
Ja und Ja.
lokale static Initialisierungen sind im GCC thread safe.
-
inter2k3 schrieb:
- nicht thread-safe vermutlich weil ein zweiter thread getSequence aufrufen könnte, während das Objekt noch konstruiert wird (aufgrund eines Aufrufs von getsequence durch ersten thread)?
Genau das. Dabei kann es dann passieren dass der 2. Thread anfängt seinerseits ebenfalls das Objekt zu initialisieren, oder dass der 2. Thread anfängt mit dem Objekt zu arbeiten, obwohl es noch nicht fertig initialisiert ist.
Inwiefern ist das für G++ in Ordnung? Hat er diesbezüglich erweiterete Sicherheitsmechanismen?
Auch richtig. G++ macht da im Prinzip das Äquivalent von boost::call_once rein um die statische Variable zu initialisieren. Da sich der check auf den meisten Plattformen so implementieren lässt, dass der "bereits initialisiert" Fall extrem schnell läuft, ist der Performance-Impact minimal.
-
@minastaros:
Wenn du C++0x benutzt und dein Singleton thread-safe machen möchtest,
kannst du dir ja mal die Variante 1 hier ankucken (bei Variante 2 wurde mir die thread-safety nicht bestätigt):
http://www.c-plusplus.net/forum/279968#2004357
-
Hallo zusammen,
vielen Dank für die vielen Anmerkungen, das first use idion werd ich nochmal näher anschauen. Wie gesagt, funktionieren tut es und in der derzeitigen Umgebung (embedded-Gerät) ist thread-safeness an dieser Stelle (noch) kein Problem, da die Threads ganz gezielt erzeugt werden und auf die Resourcen zugreifen.
Werde bei Gelegenheit mal mehr in Richtung Singleton verbessern. Xspille, auch vielen Dank für den Vorschlag. Ist das Double-Checked-Locking-Pattern, da hab ich ganz frisch was im Alexandrescu drüber geschmökert, der natürlich gleich auch den Mercedes unter den Singletons (bzw. gleich die ganze Familie) mitbringt. Naja, zur Zeit tut es noch mein
static...