Ausbruch aus der Macrohölle – Einsatz von enum classes

Ein bekanntes Problem beim Einsatz von C ist die „Macrohölle“. Werte und ganze Funktionsvorlagen werden als Macros definiert. Dieser Beitrag geht auf den Ersatz von define flags durch enum classes ein. Als Beispiel ziehe ich die Funktion open heran, welche auf unterster Ebene im Userland eine Datei öffnet (low-level call). Der Funktion werden eine Reihe von Flags übergeben, welche per OR-Operator miteinander kombiniert werden können. Dazu ein Eingangsbeispiel (Linux und Windows):

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdexcept>
#include <assert.h>	

#ifdef _WIN32
#include <io.h>          /* für MS-DOS/WIN */
#else
#include <unistd.h>      /* für UNIX */
#endif


int Open(char const* fileName, int openFlag, int permissionMode)
{
	return open(fileName, openFlag, permissionMode);
}

int main(int argc, char *argv[])
{
	int fd = Open("Test.bin", O_CREAT | O_TRUNC | O_RDWR, S_IREAD | S_IWRITE);
	
	return 0;
}

Nun wird die Zeile 16 im Debugger näher betrachtet:

Die Funktion im Debugger

Zu sehen sind der Wert von openFlag mit 0x0242 und von permissionMode mit 0x0180. Die Werte werden als OR-Operatoren über Integern zusammengeführt. Diese werden per #define vordefiniert:

#define O_ACCMODE	   0003
#define O_RDONLY	     00
#define O_WRONLY	     01
#define O_RDWR		     02
#ifndef O_CREAT
# define O_CREAT	   0100	/* Not fcntl.  */
#endif
#ifndef O_EXCL
# define O_EXCL		   0200	/* Not fcntl.  */
#endif
#ifndef O_NOCTTY
# define O_NOCTTY	   0400	/* Not fcntl.  */
#endif
#ifndef O_TRUNC
# define O_TRUNC	  01000	/* Not fcntl.  */
#endif
#ifndef O_APPEND
# define O_APPEND	  02000
#endif
#ifdef __USE_MISC
# define S_IREAD	S_IRUSR
# define S_IWRITE	S_IWUSR
# define S_IEXEC	S_IXUSR
#endif

Dieser Weg ist der typische Weg der C-Programmierung. Nun wird ein alternativer Weg in C++ aufgezeigt. Hierzu werden der Filedeskriptor und seine zugehörigen low level Funktionen als Klasse gekapselt:

/**
 Basic file operation with file descriptor.
 
 \author	Dr. Torsten Thurow
 \date		27.12.2020
 */
class LowLevelFileStream
{
protected:
	int _fd = -1;     ///> The file descriptor.
	size_t _pos = 0;  ///> The read / write position.

Innerhalb der Klasse werden nun die open flags und permission modes als enum classes realisiert:

	enum class OpenFlags
	{
		Create		= O_CREAT,				///> Creates a file and opens it for writing. Has no effect if the file specified by filename exists. The pmode argument is required when Create is specified.
		Append		= O_APPEND,				///> Moves the file pointer to the end of the file before every write operation.
		Trunc		= O_TRUNC,				///> Opens a file and truncates it to zero length. The file must have write permission. Can't be specified with ReadOnly. Trunc used with Create opens an existing file or creates a file. Note: The Trunc flag destroys the contents of the specified file.
#ifdef _WIN32
		Binary		= O_BINARY,				///> Opens the file in binary (untranslated) mode. Translations involving carriage-return and line feed characters are suppressed.
		Text		= O_TEXT,				///> Opens a file in text (translated) mode.
		Sequential	= O_SEQUENTIAL,			///> Specifies that caching is optimized for, but not restricted to, sequential access from disk.
#endif
		ReadOnly	= O_RDONLY,				///> Opens a file for reading only. Can't be specified with ReadWrite or WriteOnly.
		WriteOnly	= O_WRONLY,				///> Opens a file for writing only. Can't be specified with ReadWrite or ReadOnly.
		ReadWrite	= O_RDWR,				///> Opens a file for both reading and writing. Can't be specified with ReadOnly or WriteOnly.
	};

	enum class PermissionMode
	{
		Read		= S_IREAD,				///> Only reading permitted.
		Write		= S_IWRITE,				///> Writing permitted. (In effect, permits reading and writing.)
		ReadWrite	= S_IREAD | S_IWRITE 	///> Reading and writing permitted.
	};

Diese müssen nun nur noch um einen OR-Operator ergänzt werden:

inline TL::LowLevelFileStream::LowLevelFileStream::OpenFlags operator | (const TL::LowLevelFileStream::LowLevelFileStream::OpenFlags a, const TL::LowLevelFileStream::LowLevelFileStream::OpenFlags b)
{
	int res = (int)a | (int)b;
	return (TL::LowLevelFileStream::LowLevelFileStream::OpenFlags)res;
}

Das Ergebnis: der Aufruf der Kapselung von open ist nun typsicher und im Debugger werden statt der reinen Werte der Integers auch ihre Bedeutungen angezeigt:

und noch einmal unter Windows:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert