Klasse mit static-Variablen und Methoden verwenden
-
Ich versuche eine Klasse zu schreiben, die ich einmal in main() mit Logfile::openLogfile(LogfileName) aufrufe und dann von (fast) allen anderen Klassen aus mit Logfile::printLine(const string) aufrufen kann um Fehlermeldungen etc auszugeben.
In der Klasse sind alle Methoden und Variablen als static deklariert:
class Logfile { public: static void openLogfile(std::string); static void writeLog(std::string); static void closeLogfile(); private: static FileWriter m_filewriter; };Die Klasse verwendet eine Klasse FileWriter
class FileWriter : public FileHandlerA { public: FileWriter(const std::string); void openFile(const std::string = ""); // pure virtual in base class void closeFile(); // pure virtual in base class // unsigned int getLineCnt() const; // implemented in base class int printLine(const std::string); protected: // inherited: string m_filename, unsigned int m_uLineCnt; private: std::ofstream m_ofstream ; };Allerdings kriege ich jetzt Fehler in den static-Funktionen bei den Aufrufen der Methoden von der FileWriter-Klasse,
bspw. in der Funktionvoid Logfile::openLogfile(const string filenameP) { m_filewriter.openFile(filenameP); // constr opens file }kriege ich
undefined reference to Logfile::m_filewriterUrsprünglich wollte ich in Logfile::openLogfile(const string) den Konstruktor der FileWriter-Klasse aufrufen, der dann die Logdatei öffnen sollte,
void Logfile::openLogfile(const string filenameP) { m_filewriter(filenameP); // constructor of FileWriter opens file }aber dabei krieg ich auch eine Fehlermeldung:
**error: no match for call to '(FileWriter) (const std::string&)
**Kann die Sache vom Prinzip her überhaupt klappen? Ich bin mir nämlich grad nicht mehr sicher. Immerhin versuch ich ja, die Klasse ohne Instanziierung zu verwenden, aber trotzdem müssen doch die Werte für die Membervariablen irgendwo gespeichert werden und auch nach Beendigung der static-Funktionen erhalten bleiben (bspw LineCnt aus der Basisklasse FileHandlerA, oder der Filedescriptor).
Als Alternative könnte ich in main() ein Objekt der Logfile-Klasse erstellen und dann mit extern überall verfügbar machen, aber irgendwie scheint mir die erste Lösung eleganter.
-
upsi...das erste Problem war natürlich, dass ich vergessen habe, die static-Member-Variable zu definieren:
FileWriter Logfile::m_filewriter("LOGFILE");
Den Code kann ich damit kompilieren, also kann ich's jetzt zumindest testen.
Stephan
-
Wozu verwendest du hier eine Klasse?
Das ist ein "Javaismus".
In C++ gibt's freie Funktionen, und die sollte man auch verwenden.
-
Aber wo liegt der Vorteil von freien Funktionen? Ohne genau sagen zu können warum kommt mir die Lösung mit einer Klasse eleganter vor (wenn ich schon OOP mach dann gleich richtig...). Immerhin hat die Klasse ja drei Funktionen und 'ne Membervariable.
Dafür bräuchte man dann schon drei freie Funktionen, von denen jede auf eine globale Variable (FileWriter) Zugriff haben müsste.
Evtl. gibt's da größere Vorteile bei freien Funktionen, die ich nicht kenne?
Ganz Ohr für gute Argumente,
Stephan
-
Freie Funktionen sind unabhängig von irgendwelchen Klassen.
Was bringt dir hier eine Klasse? Nichts. Du nutzt die Möglichkeit der Klassen nicht aus, zur logischen Gliederung kannst du ebenso gut einen Namensraum nehmen. Dann können zum Beispiel auch keine sinnlosen Instanzen erstellt werden. Klassen dienen in C++ primär zur Beschreibung eines benutzerdefinierten Typs. Nur um dem Java-Prinzip (alles ist eine Klasse) gerecht zu werden, musst du in C++ keine Klasse nehmen.
Ein Beispiel sind auch die STL-Algorithmen. Diese sind als freie Funktionen implementiert, um sich nicht an eine bestimmte Klasse zu binden. Dafür arbeiten sie mit allen Containern (genauer Iteratoren), welche die entsprechende Schnittstelle unterstützen.
Radix schrieb:
(wenn ich schon OOP mach dann gleich richtig...)
Da ist C++ wohl die falsche Sprache, da sie alles andere als rein objektorientiert ist.

-
Radix schrieb:
Aber wo liegt der Vorteil von freien Funktionen?...
Sagen wir mal so: Es gibt Aufgabenstellungen, die sich besser als Objekte modellieren lassen ... und eben solche, die sich besser als Funktionen modellieren lassen.
Wenn ein Ding keinen "Lebenszyklus", keinen "Status", keine "Reaktion auf äußere Ereignisse", keine "Identität"... besitzt, finde ich es eher "vorteilsfrei", wenn man es als Objekt modelliert. So einen Quatsch wie ein "Sortierobjekt" bringt Einen einfach nicht weiter.
Radix schrieb:
Aber wo liegt der Vorteil von freien Funktionen? Ohne genau sagen zu können warum kommt mir die Lösung mit einer Klasse eleganter vor (wenn ich schon OOP mach dann gleich richtig...)....
Ich glaube, das war mal so ein Hype, zu meinen "gut modelliert" wäre gleichzusetzen mit "objektoriert modelliert".
Aber "gut modelliert" ist eben "gut modelliert" und nichts Anderes.
Nicht mißverstehen: Es gibt hervorragende Einsatzmöglichkeiten für OOP ... aber es ist auch kein Allheilmittel.Radix schrieb:
...Immerhin hat die Klasse ja drei Funktionen und 'ne Membervariable....
Ich sehe keine Membervariable in Deinem Beispiel... nur eine globale - mit allen Vor- und Nachteilen einer solchen.
Kapselung kannst Du ebenso mit einem namespace realisieren.Gruß,
Simon2.
-
@Radix:
Dein 2. Problem würde ich persönlich mit nem Zeiger lösen.
Dann kannste in der Funktion einfachdelete m_filewriter; m_filewriter = 0; m_filewriter = new FileWriter(...);schreiben.
Was die freien Funktionen angeht will ich hier garnicht viel sagen. Ich meine bloss es macht keinen Sinn eine Klasse als Namespace zu "misbrauchen". Die globale Variable kann man ohne weiteres in einem anonymous namespace packen, dann ist die auch nicht "von aussen" sichtbar, und es kann auch keiner drauf zugreifen der es nicht können sollte.
Und noch ein Tip: überdenke ob du wirklich Objekte haben willst die eine "open" und eine "close" Funktion haben. IMO verkompliziert das einiges. z.B. muss man dann in jeder Funktion abfragen "bin ich offen, nein, dann return Errorcode/throw Exception". Mit RAII umgeht man das recht elegant. Ein Objekt welches vollständig konstruiert ist, ist dann einfach per Definition offen. Und das "Schliessen" erfolgt dann einfach im Destruktor.
-
Ob ich das mit den freien Funktionen in diesem Programm noch reinnehme (nachdem es mit der Klasse eigentlich funktioniert) weiß ich nicht, da mein Klassendiagramm schon durchgewunken und für okay befunden wurde. Vielleicht doch erst beim nächsten Mal.
hustbaer schrieb:
Und noch ein Tip: überdenke ob du wirklich Objekte haben willst die eine "open" und eine "close" Funktion haben. IMO verkompliziert das einiges. z.B. muss man dann in jeder Funktion abfragen "bin ich offen, nein, dann return Errorcode/throw Exception". Mit RAII umgeht man das recht elegant. Ein Objekt welches vollständig konstruiert ist, ist dann einfach per Definition offen. Und das "Schliessen" erfolgt dann einfach im Destruktor.
Den Begriff RAII lese ich grad zum ersten Mal. So wie ich das jetzt nachgelesen habe, bedeutet es einfach dass im Konstruktor von FileWriter die Datei geöffnet wird, und im Destruktor geschlossen wird (letzteres geschieht ja autom., so dass die closeFile()-Methode in meinem Beispiel eh nicht notwendig ist). Aber damit fallen andere Prüfungen außer "Datei offen?", bspw. ob die Datei okay ist oder vielleicht von einem Benutzer gelöscht wurde, ja auch nicht weg.
Die Filewriter-Klasse wird auch noch von anderen Klassen verwendet, die viele Ausgaben produzieren. Ist das RAII-Prinzip auch dann noch sinnvoll, wenn diese Klassen regelmäßig eine Datei schließen und eine neue Datei zum Reinschreiben erstellen müssen? Das heißt dann, dass ich den FileWriter in diesen Klassen immer als Pointer und nicht als normale Membervariable drinhaben müsste, und dann müsste ich immer ein
delete m_filewriter; m_filewriter = NULL; m_filewriter = new FileWriter("new_filename");aufrufen.
-
Ob ich das mit den freien Funktionen in diesem Programm noch reinnehme (nachdem es mit der Klasse eigentlich funktioniert) weiß ich nicht, da mein Klassendiagramm schon durchgewunken und für okay befunden wurde. Vielleicht doch erst beim nächsten Mal.
Jo. Is ja auch keine Tragik. Bzw. nicht SO wichtig

(Macht ja technisch keinen Unterschied, ist bloss ne Stilfrage)Den Begriff RAII lese ich grad zum ersten Mal. So wie ich das jetzt nachgelesen habe, bedeutet es einfach dass im Konstruktor von FileWriter die Datei geöffnet wird, und im Destruktor geschlossen wird (letzteres geschieht ja autom., so dass die closeFile()-Methode in meinem Beispiel eh nicht notwendig ist).
Denke schon dass du das richtig verstanden hast.
RAII bedeutet im Prinzip dass man Resourcen mit Klassen kapselt, so dass das "Initialisieren" (Konstruieren) eines Objekts die Resource "belegt" ("anfordert"), und das zerstören des Objekts die Resource wieder freigibt. (Der zweite Teil wird oft unabhängig von RAII als RRID bezeichnet)
Solange ein solches Resource-Wrapper Objekt lebt, ist die Resource also "belegt". Dadurch spart man sich wie erwähnt die Checks ob das Objekt denn "offen" ist.
Aber damit fallen andere Prüfungen außer "Datei offen?", bspw. ob die Datei okay ist oder vielleicht von einem Benutzer gelöscht wurde, ja auch nicht weg.
Verstehe jetzt nicht ganz was du meinst.
Bei RAII würdest du irgendwo im ctor die Datei öffnen. Wenn sie aus irgendeinem Grund nicht geöffnet werden kann, dann muss der ctor eine Exception werfen, so dass das Objekt garnicht erst fertig konstruiert werden kann. Diesen Check hast du dann an genau einer Stelle, und an allen anderen fällt er weg. Entweder das Objekt konnte angelegt werden, oder nicht. Sozusagen "untote" Zombie-Objekte, die es zwar gibt, die aber nicht vollständig initialisiert sind, kann es dann nicht geben.Die Filewriter-Klasse wird auch noch von anderen Klassen verwendet, die viele Ausgaben produzieren. Ist das RAII-Prinzip auch dann noch sinnvoll, wenn diese Klassen regelmäßig eine Datei schließen und eine neue Datei zum Reinschreiben erstellen müssen?
Ja, definitiv.
Das heißt dann, dass ich den FileWriter in diesen Klassen immer als Pointer und nicht als normale Membervariable drinhaben müsste, und dann müsste ich immer ein
delete m_filewriter; m_filewriter = NULL; m_filewriter = new FileWriter("new_filename");aufrufen.
Nö.
Geht auch eleganter. Ich hab' das so geschrieben, weil ich dich nicht verwirren wollte, mit Dingen die du vielleicht noch nicht kennst, und die nicht unbedingt nötig sind um das zu beschreiben was ich beschreiben wollte.Es gibt sog. Smart Pointer mit denen sich das viel eleganter Lösen lässt.
Beispiel:boost::scoped_ptr<FileWriter> my_file; // boost::scoped_ptr<T> ist ein einfacher Smart-Pointer - siehe Boost Dokumentation für weitere Details void foo() { my_file.reset(new FileWriter(...)); // zerstört automatisch das alte Objekt /* die Zeile entspricht quasi diesem Code, wenn man es von hand schreiben würde: FileWriter* temp = FileWriter(...); delete my_file; my_file = temp; */ }
-
In der Tat, ich kenne Smart-Pointer bisher nur aus der Theorie. Im Augenblick verwende ich die boost-Bibliothek noch nicht (obwohl Smart-Pointer hilfreich wären, weil ich in einem STL-vector Pointer abspeichere, aber ich kümmere mich z.Z. selbst darum dass alles korrekt aufgeräumt wird). Ich schrecke noch ein wenig davor zurück da es kein offizieller Standard ist, aber vielleicht nehm ich's ja doch mit rein.
Auf jeden Fall schonmal vielen Dank für die Hilfe & Tipps!
-
Ich schrecke noch ein wenig davor zurück da es kein offizieller Standard ist, aber vielleicht nehm ich's ja doch mit rein.
Ist es doch. std::tr1::shared_ptr http://msdn.microsoft.com/en-us/library/bb982026.aspx