Seite 1 von 1

Singleton für Anfänger.

Verfasst: 09.09.2011, 20:00
von PhilippeSchnider
Hallo zusammen.

Nun ich kämpfe mich gerade durch die Welt von C++ da ich aus purer Neugierde einfach mal eine kleine simple DirectX 11 Anwendung erstellen möchte. Tja, sehr weit bin ich noch nicht gekommen, da stellt sich doch schon mal das erste Problem. Ich möchte eine simple Logdatei verwenden, die von den verschiedenen Komponenten verwendet werden kann, ohne immer eine Instanz übergeben zu müssen. Meine aktuelle Implementation ist, naja, unschön und sollte sicher auch nicht so implementiert werden. In diversen Tutorials wird dabei ein Singleton-Konzept verwendet. Nun aber ist es ja so, das durchaus mehere Instanzen meiner Loggerklasse erstellt werden können. Warum also ein Singleton verwenden? Aber zuerst mal der Code:

Code: Alles auswählen

#define WIN32_LEAN_AND_MEAN

#include <ctime>
#include <exception>
#include <stdexcept>
#include <fstream>
#include <string>
#include <iomanip>
#include <Windows.h>

namespace Tutorial  {

///////////////////////////////////////////////////////////////////////////////	

class Uncopyable 
{
protected:
	Uncopyable() {}
	~Uncopyable() {}

private:
	Uncopyable(const Uncopyable&);
	Uncopyable& operator=(const Uncopyable&);
};

///////////////////////////////////////////////////////////////////////////////

class CriticalSection : protected Uncopyable 
{
public:
	CriticalSection();
	~CriticalSection();
	void Enter();
	bool TryEnter();
	void Leave();

private:
	CRITICAL_SECTION mSection;
};

///////////////////////////////////////////////////////////////////////////////

CriticalSection::CriticalSection()
{
	InitializeCriticalSection(&mSection);
}

///////////////////////////////////////////////////////////////////////////////

CriticalSection::~CriticalSection()
{
	LeaveCriticalSection(&mSection);
	DeleteCriticalSection(&mSection);
}

///////////////////////////////////////////////////////////////////////////////

void CriticalSection::Enter()
{
	EnterCriticalSection(&mSection);
}

///////////////////////////////////////////////////////////////////////////////

bool CriticalSection::TryEnter() 
{
	return TryEnterCriticalSection(&mSection) == TRUE;
}

///////////////////////////////////////////////////////////////////////////////

void CriticalSection::Leave() 
{
	LeaveCriticalSection(&mSection);
}

///////////////////////////////////////////////////////////////////////////////

class Logger : protected Uncopyable
{
public:
	explicit Logger(const std::string& filename);
	~Logger();
	void WriteLine(const std::string& message);

private:
	std::ofstream mStream;
};

///////////////////////////////////////////////////////////////////////////////

Logger::Logger(const std::string& filename) : mStream(filename)
{
	if (mStream.fail())
	{
		throw std::runtime_error("Failed to create logilfe!");
	}

	WriteLine("Logger created");
}

///////////////////////////////////////////////////////////////////////////////

Logger::~Logger()
{
	WriteLine("Logger destroyed");
}

///////////////////////////////////////////////////////////////////////////////

void Logger::WriteLine(const std::string& message)
{
	__time64_t localTime;
	_time64(&localTime);

	struct tm time;
	_localtime64_s(&time, &localTime);
	
	mStream
		<< std::setfill('0')
		<< std::setw(2)
		<< time.tm_mon
		<< "."
		<< std::setw(2)
		<< time.tm_mday
		<< "."
		<< time.tm_year + 1900
		<< "/"
		<< std::setw(2)
		<< time.tm_hour
		<< ":"
		<< std::setw(2)
		<< time.tm_min
		<< ":"
		<< std::setw(2)
		<< time.tm_sec
		<< " - "
		<< message
		<< std::endl;
}

///////////////////////////////////////////////////////////////////////////////

void Logfile(const std::string& message)
{
	static CriticalSection section;
	static Logger logger("Logfile.txt");

	section.Enter();
	logger.WriteLine(message);
	section.Leave();
}

///////////////////////////////////////////////////////////////////////////////

void InvalidArgument(const std::string& message)
{
	Logfile(std::string("Invalid argument: ") + message);
	throw std::invalid_argument(message.c_str());
}

///////////////////////////////////////////////////////////////////////////////

void RuntimeError(const std::string& message)
{
	Logfile(std::string("Runtime error: ") + message);
	throw std::runtime_error(message.c_str());
}

///////////////////////////////////////////////////////////////////////////////

struct MessageInfo
{
public:
	MessageInfo();

public:
	HWND Window;
	UINT Message;
	WPARAM Param1;
	LPARAM Param2;
};

///////////////////////////////////////////////////////////////////////////////

MessageInfo::MessageInfo() : Window(nullptr), Message(0), Param1(0), Param2(0) {}

///////////////////////////////////////////////////////////////////////////////

class WindowClass : protected Uncopyable
{
protected:
	explicit WindowClass(const HINSTANCE instance);
	virtual ~WindowClass();
	virtual bool MessageHandler(MessageInfo&) { return true; }

private:
	static LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM param1, LPARAM param2);

protected:
	const HINSTANCE mInstance;
};

///////////////////////////////////////////////////////////////////////////////

WindowClass::WindowClass(const HINSTANCE instance) : mInstance(instance)
{
	if (!instance)
	{
		InvalidArgument("Invalid instance passed!");
	}

	WNDCLASSEX desc;
	desc.cbSize = sizeof(desc);
	desc.cbClsExtra = 0;
	desc.cbWndExtra = 0;
	desc.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
	desc.hCursor = LoadCursorW(nullptr, IDC_ARROW);
	desc.hIcon = LoadIconW(nullptr, IDI_APPLICATION);
	desc.hIconSm = desc.hIcon;
	desc.hInstance = mInstance;
	desc.lpfnWndProc = &WindowProc;
	desc.lpszClassName = L"WindowClass";
	desc.lpszMenuName = nullptr;
	desc.style = CS_DBLCLKS;

	if (!RegisterClassExW(&desc))
	{
		RuntimeError("Failed to register window class!");
	}
}

///////////////////////////////////////////////////////////////////////////////

WindowClass::~WindowClass()
{
	UnregisterClassW(L"WindowClass", mInstance);
}

///////////////////////////////////////////////////////////////////////////////

LRESULT CALLBACK WindowClass::WindowProc(HWND window, UINT message, WPARAM param1, LPARAM param2)
{
	// Die beim erstellen des Fensters übergebene Fensterklasseninstanz
	// speichern damit Nachrichten an diese weitergeleitet werden können.
	if (message == WM_NCCREATE)
	{
		SetWindowLongPtrW(
			window,
			GWL_USERDATA,
			reinterpret_cast<long>(
			reinterpret_cast<WindowClass*>(
			reinterpret_cast<LPCREATESTRUCTW>(param2)->lpCreateParams)));
	}

	auto callback(GetWindowLongPtrW(window, GWL_USERDATA));
	auto process(true);

	if (callback)
	{
		MessageInfo info;
		info.Window = window;
		info.Message = message;
		info.Param1 = param1;
		info.Param2 = param2;

		try
		{
			process = reinterpret_cast<WindowClass*>(callback)->MessageHandler(info);
		}
		catch (...)
		{			
			// Keine Ausnahmen zulassen.
		}
	}

	return process ? DefWindowProcW(window, message, param1, param2) : 0;
}

///////////////////////////////////////////////////////////////////////////////

class Window : protected WindowClass
{
protected:
	explicit Window(const HINSTANCE instance);
	virtual ~Window();
	void SetClientSize(UINT width, UINT height);
	void CenterAtNearestWorkArea();

protected:
	HWND mWindow;
};

///////////////////////////////////////////////////////////////////////////////

Window::Window(const HINSTANCE instance) : WindowClass(instance)
{
	// Dem Fenster die Fensterklasseninstanz übergeben damit eine abgeleitete
	// Anwendung Nachrichten verarbeiten kann.
	mWindow = CreateWindowW(
		L"WindowClass",
		nullptr,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		nullptr,
		nullptr,
		mInstance,
		reinterpret_cast<void*>(this));

	if (!mWindow)
	{
		RuntimeError("Failed to create window!");
	}

	Logfile("Window created");
}

///////////////////////////////////////////////////////////////////////////////

Window::~Window()
{
	DestroyWindow(mWindow);
	Logfile("Window destroyed");
}

///////////////////////////////////////////////////////////////////////////////

void Window::SetClientSize(UINT width, UINT height)
{
	RECT rect;
	rect.left = 0;
	rect.top = 0;
	rect.right = static_cast<long>(width);
	rect.bottom = static_cast<long>(height);

	AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);

	SetWindowPos(
		mWindow,
		nullptr, 
		0,
		0,
		static_cast<int>(rect.right - rect.left),
		static_cast<int>(rect.bottom - rect.top), 
		SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}

///////////////////////////////////////////////////////////////////////////////

void Window::CenterAtNearestWorkArea()
{
	RECT rect;
	GetWindowRect(mWindow, &rect);
            
	const auto width(rect.right - rect.left);
	const auto height(rect.bottom - rect.top);

	MONITORINFO monitor;
	monitor.cbSize = sizeof(monitor);
			
	GetMonitorInfoW(MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST), &monitor);

	SetWindowPos(
		mWindow,
		nullptr, 
		static_cast<int>((monitor.rcWork.right - width) / 2), 
		static_cast<int>((monitor.rcWork.bottom - height) / 2),
		0,
		0, 
		SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}

///////////////////////////////////////////////////////////////////////////////

class Application : protected Window
{
public:
	explicit Application(const HINSTANCE instance);
	void Update();

private:
	bool MessageHandler(MessageInfo& info);
};

///////////////////////////////////////////////////////////////////////////////

Application::Application(const HINSTANCE instance) : Window(instance)
{
	SetClientSize(800, 600);	
	SetWindowTextW(mWindow, L"Tutorial");
	CenterAtNearestWorkArea();
	ShowWindow(mWindow, SW_NORMAL);
}

///////////////////////////////////////////////////////////////////////////////

void Application::Update()
{
	// Irgendwas machen ...
}

///////////////////////////////////////////////////////////////////////////////

bool Application::MessageHandler(MessageInfo& info)
{
	bool process(true);

	switch (info.Message)
	{
	case WM_CLOSE:
		// Nicht weiterverarbeiten da anonsten die DestroyWindow Funktion
		// aufgerufen werden würde. Stattdessen eine WM_QUIT Nachricht
		// ablegen damit die Nachrichtenschleife verlassen werden kann.
		process = false;
		PostQuitMessage(0);
		break;
	}

	return process;
}

} // namespace Tutorial

///////////////////////////////////////////////////////////////////////////////

int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char*, int)
{
	auto succeeded(false);

	try
	{
		Tutorial::Application application(instance);

		MSG message;
		message.message = 0;
		
		while (message.message != WM_QUIT)
		{
			// Alle verarbeiteten Nachrichten anschliessend entfernen.
			if (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&message);
				DispatchMessageW(&message);
			}
			else
			{
				application.Update();
			}
		}

		succeeded = true;
	}
	catch (const std::exception& handler)
	{
		MessageBoxA(nullptr, handler.what(), "Exception", MB_ICONERROR);
	}

	return succeeded ? 0 : -1;
}
Wie könnte ich meine Logdatei implementieren?

Besten Dank schon mal im Voraus

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 20:04
von dot
PhilippeSchnider hat geschrieben:[...] die von den verschiedenen Komponenten verwendet werden kann, ohne immer eine Instanz übergeben zu müssen.
Warum?
PhilippeSchnider hat geschrieben:Warum also ein Singleton verwenden?
Gute Frage ;)

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 20:25
von PhilippeSchnider
Nun ich habe da an sowas wie OutputDebugString gedacht. Wäre ganz praktisch um z.B. Ausnahmen in die Logdatei schreiben zu können, siehe meine InvalidArgument Funktion.
Denn eine Komponente, die eine Ausnahme wirft, benötigt ja nicht zwangsweise auch eine Loggerinstanz. Diese aber müsste ich ja z.B. der InvalidArgument Funktion übergeben.

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 20:38
von dot
PhilippeSchnider hat geschrieben:Diese aber müsste ich ja z.B. der InvalidArgument Funktion übergeben.
Ja, wo liegt das Problem?

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 20:49
von Krishty
Die Diskussion hatten wir vor Kurzem schonmal :)

Sowas wie eine Log mag jeder anders – dot übergibt das gern als Parameter; ich bevorzuge globale Instanzen; globale Getter wären ebenfalls klug. Eine Funktion statt eines Objekts wäre auch möglich. Wir sind uns aber fast alle einig, dass Singletons für sowas eher ungeeignet sind (wenn auch nicht unbedingt völlig falsch); sie werden missverstanden und haben sich als Trend etabliert, weil man damit globale Abhängigkeiten fadenscheinig besser in OOP-Konzepte kloppen kann ;) Jedenfalls: Da gibt es keinen allgemeinen Konsens.

Gruß, Ky

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 20:57
von dot
Jap. Wobei ich inzwischen, ich glaub von hustbaer auf c++.de die, unter Umständen wirklich tolle, Idee aufgeschnappt hab, den Logger als threadlocal zu halten.

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 21:03
von Chromanoid
Gibt es für so einen Kram keine nette Lib? :D

Re: Singleton für Anfänger.

Verfasst: 09.09.2011, 21:08
von PhilippeSchnider
Hey besten Dank euch beiden. Nun ich werde wohl einfach eine Loggerinstanz übergeben, simpel und einfach.
Noch eine Frage zu meiner jetzigen Implementation:

Code: Alles auswählen

void Logfile(const std::string& message)
{
	static CriticalSection section;
	static Logger logger("Logfile.txt");

	section.Enter();
	logger.WriteLine(message);
	section.Leave();
}
Wann genau würde denn die Loggerinstanz erstellt werden? Beim ersten Aufruf der Funktion oder schon beim Programmstart?

Re: Singleton für Anfänger.

Verfasst: 11.09.2011, 01:54
von BeRsErKeR
In diesem Fall beim ersten Aufruf der Funktion, da die Instanz lokal in der Funktion angelegt wird.