Design Frage
-
Weil ich sonst nirgends fragen kann (außer halt in einem anderen Forum^^) und ich nicht viel Erfahrung mit Design habe, frage ich einfach mal hier:
Ich schreibe mir gerade eine Klasse für Ini-Dateien (weil mir die WinAPI Funktionen WritePrivateProfile... etwas eingeschränkt sind).
Die Vererbungshierarchie stellt hierbei das "dreaded diamond" dar.Bisher bestehen nur die Header, die die folgenden Klassen enthalten (hab sie für den Post teils stark gekürzt), und ich wollte wissen, ob man das Design grundsätzlich so lassen kann, oder ob es (z.B. aufgrund der virtuellen Mehrfachvererbung) furchtbar ist.
Ebenfalls würde mich interessieren, ob ich die EOpResult-Enumeration so lassen sollte, oder ob ich besser über Exceptions lösen sollte. Wenn die Enumeration passt, sollte ich dann eventuell Exceptions z.B. in der OpenIni Funktion abfangen und in einen EOpResult-Code konvertieren?Definitionen und Ini-Basisklasse:
#ifdef _UNICODE typedef std::wstring tstring; #else typedef std::string tstring; #endif enum EOpResult { RR_SUCCESS, // Variable read successfully. RR_VAR_NOT_FOUND, // Variable not found. RR_INVALID_VALUE, // Variable contains invalid value (e.g. a string when an int was supposed to be read). RR_SECTION_NOT_FOUND, // There is no section with the specified name in the Ini. RR_SECTION_NOT_SPECIFIED, // No section name was specified. RR_BUFFER_TOO_SMALL // Buffer was too small to hold the data. }; typedef std::vector<tstring> str_vector; typedef std::multimap<tstring, tstring> variable_multimap; struct SSectionData { size_t SectionPositionInFile; // The position of the section in the Ini file. variable_multimap* Variables; // A pointer to a multimap-container holding all variables of this section. }; typedef std::map<tstring, SSectionData*> section_map; //=================================================================================================== // CIniFileBase: Abstract base class for the ini file classes. class CIniFileBase { public: CIniFileBase(); virtual ~CIniFileBase() = 0; // Should load the data from the Ini file and do some other stuff. virtual bool OpenIni(const tstring& Filename, const tstring& SectionName = tstring(_T(""))) = 0; void SetUsedSectionName(const tstring& SectionName) { m_SectionName = SectionName; } const tstring& GetUsedSectionName()const { return m_SectionName; } EOpResult ListSections(str_vector& Output)const; ... // Einige weiter Funktionen, um z.B. die Variablen aufzulisten. private: tstring m_SectionName; // The active section which is used in the read/write functions (if no other section name is specified). section_map m_Sections; // Map-Container holding all sections and their data. bool bIniFileOpened; // Set to true after OpenIni() has been called. };Klasse zum Lesen von Ini Dateien:
//=================================================================================================== // CIniFileReader class: Reads variables from a Ini file. class CIniFileReader : virtual public CIniFileBase { public: CIniFileReader(); virtual ~CIniFileReader(); // Opens the specified ini file and reads the contents. The file is closed afterwards. // If the SectionName is not specified, you NEED to specify it in the Read-functions. virtual bool OpenIni(const tstring& Filename, const tstring& SectionName = tstring(_T(""))); //---------------- // Functions to read standard variables. If SectionName is specified, the saved SectionName is ignored. //---------------- EOpResult ReadBool(const tstring& VariableName, const tstring& SectionName, bool& Output)const; EOpResult ReadBool(const tstring& VariableName, bool& Output)const { return ReadBool(VariableName, m_SectionName, Output); } ... // Read Funktionen für mehr Typen, für Arrays,... };Klasse zum schreiben von Ini Dateien:
//=================================================================================================== // CIniFileWriter class: Writes variables to an Ini file. class CIniFileWriter : virtual public CIniFileBase { public: CIniFileWriter(); virtual ~CIniFileWriter(); // Opens the specified ini file and reads the contents. The actual file is not closed until // the object is destroyed or Close() is called. // If the SectionName is not specified, you NEED to specify it in the Write-functions. virtual bool OpenIni(const tstring& Filename, const tstring& SectionName = tstring(_T(""))); // Writes the data to the ini and closes the file. void CloseIni(); // Writes the data to the ini file. The whole ini is always completely written. void FlushIni(); //---------------- // Functions to write standard variables. If SectionName is specified, the saved SectionName is ignored. //---------------- EOpResult WriteBool(const tstring& VariableName, const tstring& SectionName, const bool Input); EOpResult WriteBool(const tstring& VariableName const bool Input) { WriteBool(VariableName, m_SectionName, Input); } ... // Write Funktionen für mehr Typen, für Arrays,... private: bool bAutoFlushAfterWrite; // If true, FlushIni() is called at the end of each Write() function. bool bDataChanged; // Set to true when data was written. Used to avoid unneccessary flushes. };Klasse zum lesen und schreiben von Ini Dateien:
//=================================================================================================== // CIniFile class: Can read and write variables from and to ini files. class CIniFile: public CIniFileReader, public CIniFileWriter { public: CIniFile(); virtual ~CIniFile(); // Same behaviour as CIniFileWriter::OpenIni. // Opens the specified ini file and reads the contents. The actual file is not closed until // the object is destroyed or Close() is called. // If the SectionName is not specified, you NEED to specify it in the Write-functions. virtual bool OpenIni(const tstring& Filename, const tstring& SectionName = tstring(_T(""))); };
-
Rein vom Aussehen her, siehts gut aus.
Zu deiner Frage ob enum oder exception:
Bei solchen Sachen die im "Normalfall", wenn keine Daten ausversehen mit Absicht gelöscht werden, funktionieren, sowas wie ReadBool, sollte man schon Exceptions nehmen. Denn dass ein Schlüssel fehlt ist schon eine "Ausnahme".
Bei Schlüsseln oder Dateien (wenns direkt um die Ini geht), bei denen der Entwickler schon vorher weiß, dass diese ab und zu verschwinden, sollten Funktionien wie FileExists, KeyExists, IsKeyBool, etc. implementiert werden.Auf die Rückgabe von enums hab ich mich auch verlassen. Aber wenn du viel in diesem Stil geschrieben hast, und nicht mehr alle Funktionen auswendig kennst, vergisst du ab und zu mal den Rückgabetyp einer Funktion zu überprüfen und im Ausnahmefall ist das Chaos da. Wo ist jetzt der Fehler? Wer hat RR_ERROR gesagt?
Den Fall hatte ich schonmal, ist schrecklich.
Da sind Exceptions schon besser, denn die kommen auf jeden Fall zur Geltung.Du solltest aber aufpassen, dass deine Exceptions möglichst nicht geworfen werden. Also vor den ungewissen OpenFile noch ein FileExists setzen, wenn du weißt, der Anwender ist dateilöschfreudig.
Ich hoffe ich konnte die meine Meinung halbwegs gut rüberbringen.

-
Hmm, irgendwie bloated ...
Was spricht gegen so simple Methoden wie Ini::load(...), Ini::save(...) (von einem Stream)? Zur internen Organisation: Was spricht gegen "section::varibale" als Schluessel in einer normalen Map zu verwenden (in Kombination mit einem generalisiertem Datentyp, der int, bool, string ... aufnehmen kann)?
-
@Blaze: Mh, also sicher ist es nicht, dass in der Ini Datei die Variable bereits steht. Das würde ich dann folglich per Rückgabe Code machen. Allerdings dürfte es eine Ausnahme darstellen, dass eine Variable einen ungültigen Wert enthält. D.h., hier würde eine Ausnahme angebracht sein. Wäre das nicht inkonsistentes Design, wenn man hier zweigleisig fährt?
Aufjedenfall danke
@knivil: Erm, sorry, ich verstehe deinen Post nicht ganz

Sind doch einfache Funktionen, OpenIni und CloseIni?
Wenn du mit "section::varibale" meinst, warum ich einen SSectionData-Zeiger im section_map Typ verwende: Hab mir überlegt, dass das ja genauso mit einem direkten SSectionData Objekt geht. Ändere ich noch^^
Einen generalisierten Typ kann ich nicht nehmen, weil ich nicht weiß, welche Daten eine Variable jetzt eigentlich enthält. Das muss der Klassenbenutzter wissen (Ist "MyVar=False" jetzt ein bool oder ein string?).Darum hatte ich vor, dass am Anfang die komplette Ini eingelesen und in der Map gespeichert wird (sortiert nach Sections und Variablennamen). In den Read Funktionen kann ich dann einfach die Variable heraussuchen, und von string in den korrekten Typ umwandeln.
Mit dem Schreiben habe ich mir überlegt, dass ich Änderungen erstmal nur an der Map vornehme, und in Flush dann die komplette Ini neuschreibe (der Einfachheit halber).
Ach ja, dass ich für die Variablen eine multimap (und keine map verwende) liegt daran, weil ich Arrays (z.B. einen Vektor) ebenfalls speichern möchte:
MyArray=TestStr1
MyArray=TestStr2
...
-
mfc style, ewww.
-
Erm, sorry, ich verstehe deinen Post nicht ganz

load, save, getvalue, setvalue sind 4 Methoden. Wenn du Werte als String behandelst, dann brauchst du nichtmal einen allgemeinen Typ. Du hast 4 Klassen und einen Sack voll Methoden. Keep it simple. Und ini-Dateien sind einfach. (Arrays in ini-Dateien habe ich noch nicht gesehen)
Allerdings dürfte es eine Ausnahme darstellen, dass eine Variable einen ungültigen Wert enthält. D.h., hier würde eine Ausnahme angebracht sein.
Wer bestimmt denn, was ein ungueltiger Wert ist? Die Anwendung, also ist es ein Problem des umschliessenden Kontextes und nicht Problem deiner ini-Klasse.
-
Nun ja, die Daten müssen halt in Form von Strings in der Ini gespeichert werden...
D.h. aber doch nicht, dass man Funktionen zum lesen und schreiben von z.B. Bools bereitstellen kann, welche die Konvertierungen automatisch vornehmen.
Ein bool sieht dann halt in der Ini Datei so aus: MyBool=True (oder halt False). Da keine Zusatz Informationen gespeichert werden, weiß ich selber natürlich nicht, was das jetzt für ein Datentyp ist. Allerdings kann ich ja überprüfen, wenn der Klassenbenutzer z.B. "MyBool" als int einlesen will, ob das überhaupt geht. In diesem Fall halt dann nicht --> ungültiger Wert, auf den ich ja irgendwie reagieren muss.Arrays habe ich bereits in Ini dateien gesehen, und finde sie praktisch. Wüsste nicht, was dagegen spricht. Und kompiliziert sind sie auch nicht...
-
Jojo, für was gibts denn templates/templatemethoden?? Da musst du zwar auch wahrscheinlich für alle unterstützten Type eine Spezialisierung schreiben, aber die Schnittstelle bleibt wenigstens sauber...
-
Hab ich mir auch überlegt, ob ich die Read/Write Funktionen mit Hilfe von Templates implementieren sollte.
Allerdings, müsste ich dann eben für int, bool, string, etc. spezielle Versionen anbieten. Allerdings doch dann auch für unsigned int, unsigned char, signed char, short, long,..., oder (wenn ich alle "normalen" Typen unterstützen möchte).
Außerdem: Was mache mache ich dann in der allgemeinen Version? Ich muss ja irgendwie das Objekt in einen String (und zurück) casten: Sollte ich dann z.B. einfach T.ToString() schreiben?
Ach ja, da fällt mir in dem Zusammenhang noch eine kleine Frage ein: Diese Template Funktion muss doch dann im Header stehen, oder?
-
Gugi schrieb:
Hab ich mir auch überlegt, ob ich die Read/Write Funktionen mit Hilfe von Templates implementieren sollte.
Allerdings, müsste ich dann eben für int, bool, string, etc. spezielle Versionen anbieten. Allerdings doch dann auch für unsigned int, unsigned char, signed char, short, long,..., oder (wenn ich alle "normalen" Typen unterstützen möchte).
Da wäre Überladung wohl eher was als Templatespezialisierung. Denn was bringt dir das allgemeine Template?
Du kannst versuchen, es nach dem C++-Stream-Vorbild durchzuziehen. Ich würde allerdings gleich die Streams involvieren, nämlichstd::stringstreamfür die Konvertierung von und zu Strings. Dann kannst du dir die Arbeit sparen.Gugi schrieb:
Außerdem: Was mache mache ich dann in der allgemeinen Version? Ich muss ja irgendwie das Objekt in einen String (und zurück) casten: Sollte ich dann z.B. einfach T.ToString() schreiben?
Eben, Templates sind hier ungünstig.
ToStringgibt es in C++ nicht für beliebige Typen und es gibt auch keine Konvention wie in Java, die zur Ausgabe einer Klasse eine solche Methode anbietet. Dafür kann man die Stream-Operatoren überladen. Für deinen Fall sollte es wie gesagt reichen,std::stringstreamfür die Konvertierung heranzuziehen.Gugi schrieb:
Ach ja, da fällt mir in dem Zusammenhang noch eine kleine Frage ein: Diese Template Funktion muss doch dann im Header stehen, oder?
Ja, grundsätzlich schon. Aber die Sache mit den Templates sollte sich eben erledigt haben...
-
Ich hätte in dem allgemeinen Template eine Konvertierung mittels stringstream vorgenommen. Das funktioniert schon mal für alle Elementaren Datentypen, ich kann mir also Spezialisierung (und auch Überladungen) für bit/byte/short/... und die ganzen unsigned Varianten schenken. Sind schon mal >10 Codedupletten, die wegfallen.Und für die Sachen die sich nicht via stringstream konvertieren lassen (oder auch string, was gar keine Konvertierung benötigt) eine Spezialisierung.
-
@Gugi: Irgendwie habe ich den Eindruck, dass du dein Design nur verteidigst, als ueber Alternativen nachzudenken. Wenn du nur willst, dass man dir hier auf die Schulter klopft, dann bitte: fein gemacht.
Zu Bool etc.: Ich kann einen Bool als {"True","False"} speichern, hey aber warum nicht auch als {0 , 1} oder { 'b', 'f' } oder ... alles sinnvolle Alternativen. Was davon jetzt aber benutzt wird, ist durch die Anwendung bestimmt. Ob ein Wert vorhanden sein soll, wird auch durch die Anwendung bestimmt, welche Fehler mit Werten dabei auftreten koennen , wird durch die Anwendung bestimmt, wie ein Wert interpretiert werden soll, wird von der Anwendung bestimmt. Also liegt die Verantwortung fuer alle diese Sachen beim restlichen Programm und nicht bei der Klasse CIni.
Auch braucht man keine separaten Umwandlungsfunktionen, da die Anwendung mittels Stringstream das in ein bis zwei Zeilen erledigt. Also warum extra der Aufwand?
-
knivil schrieb:
Irgendwie habe ich den Eindruck, dass du dein Design nur verteidigst, als ueber Alternativen nachzudenken. Wenn du nur willst, dass man dir hier auf die Schulter klopft, dann bitte: fein gemacht.
Das will ich nicht, aber ich will auch nicht einfach irgendetwas übernehmen, ohne verstanden zu haben, warum es jetzt besser ist als etwas anderes.
Ich habe über die Alternativen nachgedacht. Allerdings bin ich halt einfach noch ein absoluter Noob in Sachen Verwendung der Sprache. So wusste ich dass mit dem stringstream z.B. nicht. Meine Gründe gegen eine Template Version waren:- Zu viele Spezialisierungen
- Was passiert bei einem benutzerdefinierten Objekt?
- Mit direkten ReadBool, ReadInt, etc. Funktionen sieht man direkt, welche Typen unterstützt werden.
Wobei ich das mit dem stringstream Operatore Überladen nicht kapiere (ich schau die Klasse mir gleich an). Ich nehme an, dass sowas gemeint war:
MyStringStream << AnInt;Aber das hätte ich dann so geschrieben:
template <class T> EResult CIniFileWriter::Write(const string& SectionName, const string& VariableName, const T& Value) { ... MyStringStream << T; string StrToWrite = MyStringStream.str(); ... }knivil schrieb:
Zu Bool etc.: Ich kann einen Bool als {"True","False"} speichern, hey aber warum nicht auch als {0 , 1} oder { 'b', 'f' } oder ... alles sinnvolle Alternativen. Was davon jetzt aber benutzt wird, ist durch die Anwendung bestimmt. Ob ein Wert vorhanden sein soll, wird auch durch die Anwendung bestimmt, welche Fehler mit Werten dabei auftreten koennen , wird durch die Anwendung bestimmt, wie ein Wert interpretiert werden soll, wird von der Anwendung bestimmt. Also liegt die Verantwortung fuer alle diese Sachen beim restlichen Programm und nicht bei der Klasse CIni.
Das hier verstehe ich z.B. einfach nicht bzw. sehe ich nicht ein. Wenn ich ein Programm schreibe, und z.B. eben ein Bool in eine Ini Datei speichern möchte, dann ist es doch eigentlich dem Programm egal, wie es gespeichert wird. Es soll einfach ein Bool möglichst einfach geschrieben werden. Hierfür habe ich die Ini Klassen ja, damit ich mir die Konvertierungen sparen kann. Beim Lesen doch das gleiche: Ich möchte einfach z.B. ein Int aus einer Ini Datei lesen, ohne dass ich den string noch Konvertieren muss. Allerdings können natürlich nicht alle strings in Ints konvertiert werden und das Programm muss ja auch eine Möglichkeit haben, auf fehlende oder falsche Werte zu reagieren. Da Ini Dateien aufgrund ihrer Text-Art leicht von Benutztern editiert werden können, kann es gut sein, dass eine Variable nicht in der Ini steht bzw. ein falscher Wert.
Für das benutzterdefinierte Speichern gibt es ja immer noch die String-Version.
-
dann ist es doch eigentlich dem Programm egal, wie es gespeichert wird
Ach und der ini-Datei ist es nicht egal? Was in der Datei steht, ist doch der Datei egal. Es geht darum, wer welche Aufgaben/Verantwortung hat. Falls ein Wert fehlt, so kann die ini-Datei ja nichts dafuer. Also sollte sie nur sagen, dass der Wert nicht gefunden wurde. Wie damit umgegangen wird, entscheidet die Anwendung. Und welches Format fuer z.B. Bool gewaehlt wird, ist der Anwendung zwar egal, aber deswegen kann sie sich ja eins aussuchen.
Hierfür habe ich die Ini Klassen ja, damit ich mir die Konvertierungen sparen kann.
Ach, deine Ini-Klasse ist auch fuer Konvertierungen zustaendig? Du kannst Konvertierungen voellig unabhaengig von der ini-Klasse definieren. Und da es unabhaengig ist, hat sowas auch nichts in der Klasse zu suchen. Die Kopplung zwischen Lesen/Schreiben als auch Typumwandlung ist voellig unnoetig. Fuer fuer folgende Zeilen brauche ich keine Extrafunktion:
std::stringstream str; str << a number; str >> a string;z.B. ein Int aus einer Ini Datei lesen, ohne dass ich den string noch Konvertieren muss.
Du wirst ihn immer konvertieren muessen, aber ich wuerde es nicht in der ini-Klasse machen.
-
knivil schrieb:
Und welches Format fuer z.B. Bool gewaehlt wird, ist der Anwendung zwar egal, aber deswegen kann sie sich ja eins aussuchen.
Wieso sollte die Anwendung sich mit der Frage belasten, wie jetzt ein Bool gespeichert wird? Bzw., was verstehst du unter "Anwendung"? Ich würde es hier auf alles außerhalb der Ini-Klassen beziehen... (womit ich wsk falsch liege, da dies auch die ReadBool Funktion im 1. C++ Code unten mit in die "Anwendung" mit einschließen würde).
Würdest du dann einfach nur jeweils eine Funktion zum Lesen bzw. Schreiben anbieten, die den Wert in Form eines Strings zurückgibt, und dann überall im Code, wo eine Konvertierung nötig ist, die 3 Zeilen mit dem stringstream schreiben? Oder in einer extra Funktion, welche so aussähe:
EOpResult ReadBool(CIniFileReader& IniFile, const string& Section, const string& Variable);?
Irgendwie empfinde ich es aber als besser (bzw. natürlicher), dies in der Klasse zu haben, um die Art der Speicherung zu abstrahieren...
Bzw. finde ich folgenden Code natürlicher:CIniFileReader IniFileObject; ... bool bReadBool; if (IniFileObject.ReadBool("MySection", "MyVariable", bReadBool) != RR_SUCCESS) { Do something. }als
CIniFileReader IniFileObject; ... bool bReadBool; if (ReadBool(IniFileObject, "MySection", "MyVariable", bReadBool) != RR_SUCCESS) { Do something. }Das 2. sieht mehr nach C als C++ aus, oder?
Aber das Argument, dass die Funktion auch außerhalb der Klasse realisierbar ist, ist wahrscheinlich das stärkere. Steht auch in Meyers Effective C++, so weit ich weiß.
Aber sollte man dann alle Funktionen, welche auch außerhalb einer Klasse realisierbar sind, auch außerhalb definieren? (Public Funktionen, OK, aber was ist dann z.B. mit privaten Funktionen, welche nur "Hilfsfunktionen" sind, die auf keine Datenelemente/Methoden zugreifen und dadurch entstanden sind, dass aus 1 anderen Funktion aufgrund des Funktionsumfangs Code ausgelagert wurde?)
Oh mann, ich komme mir bei solchen Entscheidungen immer dämlich vor, weil ich nie weiß, was ich jetzt machen soll. Das gleiche gilt für Variablen/Funktionsbenennung oder ähnliches xD
-
Ich wuerde eine Funktion anbieten fuers setzen von Strings. Dann vielleicht sowas wie string2bool oder int2string fuer Konvertierungen. Das hat den Vorteil, dass du die gleichen Funktionen auch fuer Logging, Usermessages, Eingabe und Ausgabe verwenden kannst. Ich favorisiere natuerlich noch eine einzelne Ini-Klasse mit load, save, getValue und setValue, die nur mit Strings arbeiten.
Signaturbeispiel fuer eine losgeloeste Konvertierungsfunktion:bool int2string( int, std::& string )std::string ist ein In/Out-Parameter und bool zeigt Fehler an. Kann man auch mit Exceptions machen, aber da scheiden sich die Geister.
Ich halte mich meist an die Richlinien: lose Kopplung, keine Aufgaben vermischen, keine Codeduplizierung, RAII. Das ist natuerlich der Idealzustand und es sind nur Richtlinien. Auch entstehen durch diese Richtlinien Gefahren wie: premature generalisation, overengineering und solving the wrong problem.
Oh mann, ich komme mir bei solchen Entscheidungen immer dämlich vor, weil ich nie weiß, was ich jetzt machen soll.
Quatsch. Das meiste sind nur Vorlieben und die entstehen aus Erfahrung. Auch was ich hier zum besten gebe. Mach einfach deine eigenen. Ich mag z.B. nicht diesen Reader/Writer-Kram, da das irgendwie immer nach Java aussieht.

-
Also, dann ziehe ich jetzt sämtliche Read/Write Funktionen (bis auf die string Version, evtl. noch umbenennen) aus den Klassen raus und wandle sie in Templates mit Spezialisierungen um. Mehr oder weniger so:
template <class T> EOpResult ReadIniVar(const CIniFileReader& IniFile, const string& Section, const string& Variable, T& Output) { string ReadValue; const EOpResult Result = IniFile.GetValue(Section, Variable, ReadValue); if (Result != RR_SUCCESS) return Result; stringstream StringStream; StringStream << ReadValue; StringStream >> Output; //... Error checking return Success or Failure (falls Konvertierung fehlschlug). } template <class T> EOpResult WriteIniVar(const CIniFileWriter& IniFile, const string& Section, const string& Variable, const T& Input) { stringstream StringStream; StringStream << Input; return IniFile.SetValue(Section, Variable, StringStream.str()); }Aufjedenfall viele Dank für die geduldigen Antworten

(Mh, eine Frage hätte ich dann da noch xD :
Gugi schrieb:
Aber sollte man dann alle Funktionen, welche auch außerhalb einer Klasse realisierbar sind, auch außerhalb definieren? (Public Funktionen, OK, aber was ist dann z.B. mit privaten Funktionen, welche nur "Hilfsfunktionen" sind, die auf keine Datenelemente/Methoden zugreifen und dadurch entstanden sind, dass aus 1 anderen Funktion aufgrund des Funktionsumfangs Code ausgelagert wurde?)
)
-
Gugi schrieb:
Aber sollte man dann alle Funktionen, welche auch außerhalb einer Klasse realisierbar sind, auch außerhalb definieren? (Public Funktionen, OK, aber was ist dann z.B. mit privaten Funktionen, welche nur "Hilfsfunktionen" sind, die auf keine Datenelemente/Methoden zugreifen und dadurch entstanden sind, dass aus 1 anderen Funktion aufgrund des Funktionsumfangs Code ausgelagert wurde?)
Wenn die Funktion nur im Zusammenhang mit der Klasse Sinn macht, und auch nicht in die öffentliche Schnittstelle passt, würde ich eine der folgenden Varianten vorziehen:
a) Private Klassenmethode
b) Deklaration/Implementation ausschließlich in der cpp (ggf. noch in einen anonymen Namensraum)Inzwischen tendiere ich eher zu b, da ich im Header eine Codedokumentation (Doxygen) mache, und keine unnötigen Methoden/Funktionen aufführen will.
-
k, danke
