<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Modernes Exception-Handling Teil 2 - Hinter den Kulissen]]></title><description><![CDATA[<h1>Modernes Exception-Handling Teil 2 - Hinter den Kulissen</h1>
<p>Betrachten wir einmal, was hinter den Kulissen vor sich geht. Wie implementieren Compiler diesen Exception-Mechanismus? Als Beispiel nehmen wir den VC++ 2005 heran. Mit VC++ 2008 hat sich in Bezug auf Exceptionhandling nichts Grundlegendes verändert, es wurden nur ein paar kleine Optimierungen eingebaut - die Struktur blieb aber gleich.</p>
<p>Sehen wir uns folgenden einfachen Code an:</p>
<pre><code class="language-cpp">class Class {};

void do_nothing() {
}

void might_throw() {
	if(rand() % 2) {
		throw SomeException();
	}
}

void two_objects() {
	Class objA;
	might_throw();
	Class objB;
	might_throw();
	Class objC;
}

void pass_through() {
	two_objects();
}

void foo() {
	try {
		pass_through();
	}
	catch(SomeException&amp; e) {
		do_nothing();
		throw;
	}
}

int main() {
	foo();
}
</code></pre>
<p>Der Compiler muss im Falle einer Exception genau wissen, was er zu tun hat - er muss deshalb irgendwo Informationen haben, wo das Programm steht und was es zu tun gibt. Unter Windows 32 Bit ist das ganze per einfach verketteter Liste gelöst, in der alle Funktionen stehen, die im Falle einer Exception etwas tun müssen. Sehen wir uns diese Liste einmal an:<br />
<img src="https://static.c-plusplus.net/magazine/images/exceptions/win32_exception_frame_data.png" alt="" class="img-responsive img-markdown" /></p>
<p><code>might_throw</code> wirft eine Exception, aber es gibt nichts zu tun. Keine Objekte, die zerstört werden müssen und keine catch-Blöcke. <code>two_objects</code> braucht dagegen einen Eintrag in der Unwind-Liste. Denn <code>two_objects</code> erstellt Objekte, die zerstört werden müssen, wenn eine Exception fliegt. <code>pass_through</code> hat keinen Eintrag in der Liste, denn egal was passiert, <code>pass_through</code> leitet die Exception nur weiter, ohne selbst zu reagieren. <code>foo</code> dagegen hat einen catch-Block und muss somit Code ausführen, wenn eine Exception fliegt. Der catch-Block enthält Code, um zu überprüfen, ob die Exception hier gefangen wird oder durchgelassen wird. Selbst wenn <code>foo</code> die Exception nicht fangen würde, müsste dieser Code dennoch ausgeführt werden. <code>main</code> ist wie <code>pass_through</code> recht langweilig. <code>mainCRTStartup</code> ist eine magische Funktion der C/C++-Runtime. Hier werden globale Variablen wie <code>errno</code> initialisiert, der Heap angelegt, <code>argc</code> / <code>argv</code> gefüllt, etc. und ebenfalls ein try-catch-Block um <code>main</code> gelegt.</p>
<p>Jedes Mal, wenn eine Funktion betreten wird, wird ein Eintrag in der Unwind-Liste gemacht. Da aber einige Funktionen keinen Code haben, der abhängig von Exceptions ist, werden diese Funktionen nicht in der Liste eingetragen. Dieser Eintrag kostet natürlich Zeit und Speicher, deshalb optimiert der Compiler wo er nur kann. &quot;Static Data&quot; enthält Daten zu den jeweiligen Funktionen, die den aktuellen Status angeben.</p>
<p>Die interessante Funktion ist <code>two_objects</code> . Sehen wir uns <code>two_objects</code> einmal so an, wie ein sehr naiver Compiler sie implementieren könnte:</p>
<pre><code class="language-cpp">void two_objects() {
	//Metadaten
	//Eintrag in Unwind Liste
	Function this = new Function();
	unwindList.add(&amp;this);
	this.status=0;

	//Objekte anlegen:
	Class* objA;
	Class* objB;
	Class* objC;

	//Class objA; - objekt initialisieren
	objA=new Class();
	this.status=1;

	might_throw();
	if(exception) goto cleanup;

	//Class objB; - objekt initialisieren
	objB=new Class();
	this.status=2;

	might_throw();
	if(exception) goto cleanup;

	//Class objC; - objekt initialisieren
	objC=new Class();
	this.status=3;

	//aufräumen
cleanup:
	if(this.status==3) {
		delete objC;
		this.status=2;
	}
	if(this.status==2) {
		delete objB;
		this.status=1;
	}
	if(this.status==1) {
		delete objA;
		this.status=0;
	}
	if(this.status==0) {
		unwindList.remove(&amp;this);
		delete this;
	}
}
</code></pre>
<p>Der Cleanup-Code wird aus dem &quot;Static Data&quot;-Block abgeleitet, auf den das aktuelle Unwind-Element zeigt. Diese Statusvariable immer auf dem Laufenden zu halten, kostet ebenfalls Zeit und wird deshalb nur dann gemacht, wenn es wirklich notwendig ist. <code>this.status=3;</code> wäre in dem obigen Code wegoptimierbar.</p>
<p>Unter Windows 64 Bit sieht das ganze schon etwas besser aus. Statt einer Unwind-Liste haben wir eine Unwind-Map und als Schlüssel verwenden wir den Wert des Instruction Pointers, IP, zu dem Zeitpunkt, als die Exception geworfen wurde.</p>
<p><img src="https://static.c-plusplus.net/magazine/images/exceptions/win64_exception_frame_data.png" alt="" class="img-responsive img-markdown" /></p>
<p>Der Vorteil hier liegt auf der Hand: wir haben keine teure Liste, die wir pro Funktionsaufruf ändern müssen, wir haben nur eine komplexe Map, die wir einmal erstellt haben. Über den IP kann man den aktuellen Status der Funktion (und vor allem auch die Funktion selbst, in der man sich gerade befindet) herausfinden. Der Nachteil liegt im höheren Aufwand, falls eine Exception geworfen wird. Da Exceptions aber nur fliegen, wenn sowieso etwas nicht mit rechten Dingen zugeht, ist es durchaus vertretbar - vor allem, wenn man dafür den Nicht-Exception-Pfad deutlich beschleunigen kann.</p>
<p>Das ganze soll nicht vor Exceptions abschrecken - sie sind zwar nicht gratis (aus Performance-Sicht), aber man darf nicht vergessen, dass ein <code>if()</code> oder gar ganze <code>switch()</code> -Orgien bei der if-then-else-Fehlerbehandlung auch nicht gerade wenig Zeit kosten.</p>
<p>Wenn nun eine Exception geworfen wird, kann die Funktion sich selbst dank der Statusinformationen unwinden - aber wie genau wird der passende catch-Handler gefunden? VC++ geht hier einen 2-Pass-Weg, es wird die Unwind-Liste also zweimal durchgegangen.</p>
<p>Beim 1. Pass wird ein passender Exceptionhandler gesucht. Wenn keiner gefunden wird, wird <code>terminate()</code> aufgerufen, welches <code>abort()</code> aufruft und das Programm beendet. Wenn aber einer gefunden wurde, dann wird der 2. Pass ausgeführt, in welchem das Unwinding beginnt.</p>
<h2>Exkurs: Exceptions in C</h2>
<p>Im vorherigen Abschnitt haben wir einen Blick hinter die Kulissen des Exceptionhandlings geworfen. Doch C bzw. C++ wären nicht C bzw. C++ wenn man das ganze nicht auch händisch implementieren könnte. Einige Exceptionimplementierungen basieren auf genau dem Prinzip, das Sie gleich kennenlernen werden. Vor allem im Embedded Bereich, wo öfters C++-Exceptions nicht verwendet werden können, setzt man oft C-Exceptions ein.</p>
<p>Der Trick hinter Exceptions in C sind die beiden Funktionen <code>setjmp()</code> und <code>longjmp()</code> , deshalb wird diese Art der Implementierung auch gerne <em>sjlj-Exceptions</em> genannt.</p>
<p><code>setjmp</code> sichert den aktuellen Kontext in einen sogenannten jump-Buffer. Der Kontext enthält unter anderem die auto-Variablen am Stack und die Registerwerte. <code>setjmp</code> liefert immer 0 als Ergebnis. Nun verwendet man <code>longjmp</code> um einen Kontext wiederherzustellen (einschließlich des Instruction Pointers), wir landen mit der Ausführung also wieder in der Zeile, in der wir <code>setjmp()</code> aufgerufen haben. <code>longjmp</code> geben wir aber einen bestimmten Integer-Wert mit und diesen liefert <code>setjmp</code> uns jetzt - so können wir zwischen den einzelnen Fällen unterscheiden.</p>
<p>Da das sehr theoretisch klingt, ein kleines Beispiel:</p>
<pre><code class="language-cpp">#include &lt;stdio.h&gt;
#include &lt;setjmp.h&gt;
#include &lt;assert.h&gt;

jmp_buf jbuf;

#define E_DIVBYZERO -1
#define E_NOCLEANDIV -2

int divide(int a, int b) {
	if(b==0) {
		longjmp(jbuf, E_DIVBYZERO);
	}
	if(a%b != 0) {
		longjmp(jbuf, E_NOCLEANDIV);
	}
	return a/b;
}

int main() {

	switch(setjmp(jbuf)) {
	case 0:
		{
			int a,b,c;
			puts(&quot;please input an integer&quot;);
			scanf(&quot;%d&quot;, &amp;a);
			puts(&quot;please input another integer&quot;);
			scanf(&quot;%d&quot;, &amp;b);

			c=divide(a, b);
			printf(&quot;%d divided by %d gives %d\n&quot;, a, b, c);
			return 0;
		}
	case E_DIVBYZERO:
		{
			fputs(&quot;The integers couldn't be divided, due to a division by zero error.\n&quot;, stderr);
			return -1;
		}
	case E_NOCLEANDIV:
		{
			fputs(&quot;The integers couldn't be divided without a remainder.\n&quot;, stderr);
			return -1;
		}
	default:
		assert(0);
	}
	assert(0);
}
</code></pre>
<p><code>divide()</code> dividiert 2 Integer-Werte und liefert einen Fehler, wenn der Divisor 0 ist oder die Division einen Rest ergibt. Sobald wir eine Fehlersituation in <code>divide</code> haben, springen wir mit <code>longjmp</code> in das switch in main. Dort wird das Ergebnis ausgewertet und der passende Case-Zweig angesprungen.</p>
<p>Ein Wort der Warnung ist hier aber angebracht: lesen Sie genau in ihrer Compilerdokumentation nach, wie sich <em>sjlj</em> in einem C++-Programm verhält. Denn in einem C++-Programm muss der Destruktor von Objekten am Stack ausgeführt werden (etwas das wir uns in C ja sparen können).</p>
<p>Einen etwas tieferen Einblick in sjlj-Exceptions bieten Ihnen <a href="http://www.on-time.com/ddj0011.htm" rel="nofollow">Tom Schotland und Peter Petersen</a>.</p>
<h2>Exception-Safety testen</h2>
<p>Wie können wir garantieren, dass unsere Klassen exceptionsicher sind? Wir können natürlich den Code stundenlang analysieren und irgendwann sagen: so, jetzt haben wir alle Situationen bedacht. Das ist aber unpraktisch und in der Software-Entwicklung lechzen wir nach Automatisierungen.</p>
<p>Unittest: eine komplette Automatisierung ist mir leider nicht bekannt, aber es gibt Techniken, die man in seine Unittests einbauen kann. Die Idee ist eine Funktion <code>mightThrow()</code> in jede Funktion zu packen, die eine Exception werfen darf. Einfach ist das ganze, wenn wir z.B. einen Container oder ähnliches testen wollen:</p>
<pre><code class="language-cpp">class ThrowTestClass {
private:
	int value;
public:
	TestClass(int value=0) 
	: value(value) {
		mightThrow();
	}

	TestClass(TestClass const&amp; other)
	: value(other.value) {
		mightThrow();
	}

	int operator=(TestClass const&amp; other) {
		this-&gt;value = other.value;
		mightThrow();
	}
	//...
};

int main() {
	std::vector&lt;ThrowTestClass&gt; vec;
	test(vec);
}
</code></pre>
<p>Die ganze Magie befindet sich in der Funktion <code>mightThrow</code> .</p>
<pre><code class="language-cpp">void mightThrow() {
	if(!throwCounter--) {
		throw ExceptionSafetyTestException();
	}
}
</code></pre>
<p>Wir nehmen eine globale Variable und reduzieren sie immer um 1, wenn <code>mighThrow</code> aufgerufen wird. Wenn <code>throwCounter</code> 0 erreicht hat, dann wird eine Exception geworfen. Idealerweise iteriert <code>mightThrow</code> dann durch alle Exceptions, die die Funktion werfen darf, meistens ist das aber zu viel des Guten und es reicht eine Standard-Exception zu werfen. Sehen wir uns dazu jetzt die Testfunktion an:</p>
<pre><code class="language-cpp">template&lt;class Data, class Test&gt;
void basicGuaranteeCheck(Data&amp; data, Test const&amp; test) {
	bool finished=false;
	for(int nextThrowCounter=0; !finished; ++nextThrowCounter) {
		Data copy(data);
		throwCounter = nextThrowCounter;
		try {
			test.test(copy);
			finished=true;
		} catch(ExceptionSafetyTestException&amp; e) {
			//nothing
		}
		invariants(copy);
	}
}

template&lt;class Data, class Test&gt;
void strongGuaranteeCheck(Data&amp; data, Test const&amp; test) {
	bool finished=false;
	for(int nextThrowCounter=0; !finished; ++nextThrowCounter) {
		Data copy(data);
		throwCounter = nextThrowCounter;
		try {
			test.test(copy);
			finished=true;
		} catch(ExceptionSafetyTestException&amp; e) {
			REQUIRE(copy == data);
		}
		invariants(copy);
	}
}
</code></pre>
<p>Mit Hilfe von Regular Expressions lässt sich die Dokumentation des Codes dazu nutzen, die notwendigen throws zu generieren. Dabei wird auf einer Kopie des originalen Source Codes gearbeitet und am Anfang jeder Funktion, die Exceptions werfen darf, ein <code>mightThrow()</code> eingefügt.</p>
<p>Leider kenne ich keine Unittest-Library, die das unterstützt - aber vielleicht regt dieser Artikel ja den einen oder anderen an, so etwas in bestehende Librarys reinzupatchen.</p>
<p>Der Code der Testfunktion sollte leicht verständlich sein, deshalb sehen wir ihn uns nur kurz näher an. <code>data</code> ist ein Datenobjekt, z.B. ein Objekt einer Klasse, und <code>test</code> ist ein Objekt, das den Test ausführt. So könnte <code>data</code> z.B. ein <code>std::vector</code> sein und <code>test</code> könnte den operator= testen. Der throwCounter ist eine globale Variable, die bestimmt, wann mightThrow eine Exception wirft und anhand <code>finished</code> erkennen wir, wann keine Exception mehr geworfen wurde (und deshalb der Test beendet ist). Wir arbeiten dabei die ganze Zeit nur auf einer Kopie der echten Daten, da wir ja (zumindest bei der Strong-Garantie) testen wollen, ob der Zustand trotz Exception identisch geblieben ist. Mit <code>invariants()</code> überprüfen wir zum Schluss, ob die Invarianten noch alle stimmen.</p>
<h2>Interoperability von Exceptions</h2>
<p>In C++ leiden wir unter dem Fehlen eines <a href="http://en.wikipedia.org/wiki/Application_binary_interface" rel="nofollow">ABI</a>-Standards. Wir können leider nicht garantieren, dass eine Exception, die ein Binary (z.B. eine DLL oder SO) verlässt kompatibel mit den Exceptions in dem Binary ist, dass die Exception fängt. Natürlich ist es möglich, diese Kompatibilität zu erzwingen und in einigen Situationen macht das auch durchaus Sinn, aber wir sollten nicht davon ausgehen, dass dies immer zutrifft. Wir haben in C++ also das Problem, dass wir Exceptions nicht über Binary-Grenzen hinweg werfen dürfen. Wir müssen in solchen Situationen zu dem alten if-then-Error-Handling zurückkehren.</p>
<p>Java und .NET haben dieses Problem nicht, da sie jeweils ein standardisiertes ABI haben und daher das Werfen und Fangen von Exceptions über Binary-Grenzen hinweg kein Problem darstellt.</p>
<h2>Exceptionsicheres Klassendesign</h2>
<p>Nach welchen Richtlinien schreibt man denn nun exceptionsichere Klassen? Das Paradebeispiel dafür ist eine Stack-Klasse wie <code>std::stack</code> - wobei <code>std::stack</code> ja eigentlich nur ein <a href="http://de.wikipedia.org/wiki/Adapter_(Entwurfsmuster)" rel="nofollow">Container-Adapter</a> ist. Eine naive Implementierung einer Stack-Klasse könnte so aussehen:</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class Stack {
private:
	T* data;
	std::size_t used;
	std::size_t space;

public:
	explicit Stack(std::size_t expectedElements = 100)
	: data(static_cast&lt;T*&gt;(operator new(expectedElements*sizeof T)))
	, used(0)
	, space(expectedElements) {
	}

	Stack(Stack const&amp; other)
	: data(static_cast&lt;T*&gt;(operator new(other.used*sizeof T)))
	, used(other.used)
	, space(other.used) {
		std::uninitialized_copy(other.data, other.data+other.used, data);
	}

	~Stack() {
		std::destroy(data, data+used);
		operator delete(data);
	}

	Stack&amp; operator=(Stack&amp; const other) {
		Stack temp(other);
		swap(temp);
		return *this;
	}

	void push(T const&amp; obj) {
		if(space&gt;used) {
			std::consruct(data+used, obj);
			++used;
			return;
		}

		space*=2+1;
		T* temp=operator new(space*sizeof T);
		std::uninitialized_copy(data, data+used, temp);
		std::construct(temp+used, obj);
		std::swap(data, temp);
		std::destroy(temp, temp+used);
		operator delete(temp);
		++used;
	}

	T pop() {
		if(empty())
			throw StackEmptyException();
		T temp(data[--used]);
		std::destroy(data+used);
		return temp;
	}

	bool empty() const {
		return used==0;
	}

	std::size_t size() const {
		return used;
	}

	void swap(Stack&amp; other) {
		std::swap(data, other.data);
		std::swap(used, other.used);
		std::swap(space, other.space);
	}
};
</code></pre>
<p>Hier gibt es eine Menge Probleme. Gehen wir sie der Reihe nach an:</p>
<pre><code class="language-cpp">Stack(Stack const&amp; other)
	: data(static_cast&lt;T*&gt;(operator new(other.used*sizeof T)))
	, used(other.used)
	, space(other.used) {
		std::uninitialized_copy(other.data, other.data+other.used, data);
	}
</code></pre>
<p>Sollte eine Kopieroperation in <code>std::uninitialized_copy</code> fehlschlagen, so wird der Speicher, auf den data zeigt, nicht aufgeräumt.</p>
<pre><code class="language-cpp">void push(T const&amp; obj) {
		if(space&gt;used) {
			std::consruct(data+used, obj);
			++used;
			return;
		}

		space*=2+1;
		T* temp=operator new(space*sizeof T);
		std::uninitialized_copy(data, data+used, temp);
		std::construct(temp+used, obj);
		std::swap(data, temp);
		std::destroy(temp, temp+used);
		operator delete(temp);
		++used;
	}
</code></pre>
<p><code>space</code> wird erhöht, bevor die Kopieroperationen beendet sind. Sollte <code>new</code> oder <code>std::copy()</code> fehlschlagen, bleibt <code>space</code> auf dem erhöhten Wert, obwohl keine Erhöhung stattfand.</p>
<pre><code class="language-cpp">T pop() {
		if(empty())
			throw StackEmptyException();
		T temp(data[--used]);
		std::destroy(data+used);
		return temp;
	}
</code></pre>
<p>Sollte eine der beiden Kopieroperation fehlschlagen, geht das Objekt für immer verloren, da wir es bereits aus unserem Stack gelöscht haben - es aber nie beim Caller ankam.</p>
<p>Eine elegantere Variante diese Probleme zu umgehen, wäre folgende Implementierung:</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class StackImpl {
public:
	T* data;
	std::size_t used;
	std::size_t space;

	explicit StackImpl(std::size_t elements)
	: data(static_cast&lt;T*&gt;(operator new(elements*sizeof T)))
	, used(0)
	, space(elements) {
	}

	~StackImpl() {
		std::destroy(data, data+used);
		operator delete(data);
	}

	void swap(StackImpl&amp; other) {
		std::swap(data, other.data);
		std::swap(used, other.used);
		std::swap(space, other.space);
	}

private:
	StackImpl(StackImpl const&amp;);
	StackImpl&amp; operator=(StackImpl&amp; const);

};

template&lt;typename T&gt;
class Stack {
private:
	StackImpl&lt;T&gt; impl;

public:
	explicit Stack(std::size_t expectedElements = 100)
	: impl(expectedElements) {
	}

	Stack(Stack const&amp; other)
	: impl(other.impl.used) {
		std::uninitialized_copy(other.impl.data, other.impl.data+other.impl.used, impl.data);
		impl.used=other.impl.used;
	}

	Stack&amp; operator=(Stack&amp; const other) {
		Stack temp(other);
		swap(temp);
		return *this;
	}

	void push(T const&amp; obj) {
		if(impl.space == impl.used) {
			Stack temp(impl.space*2+1);
			std::unitialized_copy(impl.data, impl.data+impl.used, temp.impl.data);
			temp.impl.used=impl.used;
			swap(temp);
		}
		std::construct(impl.data+impl.used, obj);
		++impl.used;
	}

	void pop() {
		if(empty())
			throw StackEmptyException();
		std::destroy(impl.data+impl.used-1);
		--impl.used;
	}

	T&amp; top() {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}

	T const&amp; top() const {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}

	bool empty() const {
		return impl.used==0;
	}

	std::size_t size() const {
		return impl.used;
	}

	void swap(Stack&amp; other) {
		impl.swap(other.impl);
	}
};
</code></pre>
<p>Da wir eine Hilfsklasse verwenden, die das Speichermanagement übernimmt, entstehen im Konstruktor keine Speicherlecks mehr:</p>
<pre><code class="language-cpp">Stack(Stack const&amp; other)
	: impl(other.impl.used) {
		std::uninitialized_copy(other.impl.data, other.impl.data+other.impl.used, impl.data);
		impl.used=other.impl.used;
	}
</code></pre>
<p>Sollte <code>std::uninitialized_copy</code> fehlschlagen, wird dennoch <code>impl</code> zerstört und der Speicher korrekt freigegeben. Wichtig ist, dass <code>used</code> erst gesetzt wird, nachdem das Kopieren erfolgreich war.</p>
<pre><code class="language-cpp">void push(T const&amp; obj) {
		if(impl.space == impl.used) {
			Stack temp(impl.space*2+1);
			std::unitialized_copy(impl.data, impl.data+impl.used, temp.impl.data);
			temp.impl.used=impl.used;
			swap(temp);
		}
		std::construct(impl.data+impl.used, obj);
		++impl.used;
	}
</code></pre>
<p>Wir verwenden hier das bekannte Copy&amp;Swap, um den Speicherbereich zu vergrößern. Wir reduzieren dadurch den nötigen Code und gewinnen Robustheit.</p>
<pre><code class="language-cpp">void pop() {
		if(empty())
			throw StackEmptyException();
		std::destroy(impl.data+impl.used-1);
		--impl.used;
	}

	T&amp; top() {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}

	T const&amp; top() const {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}
</code></pre>
<p>Ein <code>pop()</code> , das den gepopten Wert by Value liefert, kann nie exceptionsicher sein. Wir brauchen daher eine <code>top()</code> -Methode, um an das oberste Element zu gelangen. Nebenbei gewinnen wir dadurch noch die Möglichkeit Stack als ein konstantes Objekt verwenden zu können, da wir nun mit <code>top()</code> an das oberste Element kommen, ohne den Stack ändern zu müssen.</p>
<h2>Design von Exceptionklassen</h2>
<p>Je nachdem mit welcher Sprache man arbeitet, sehen Exceptions immer leicht anders aus. Exceptions können das Debuggen erleichtern, wenn sie wichtige Informationen wie <em>Was ist passiert?</em>, <em>Wo ist es passiert?</em> und u.U. auch ein <em>Warum ist es passiert?</em> mitteilen. Das essentiellste davon ist &quot;Was ist passiert?&quot;. In der C++-Standard-Library über die virtuelle Funktion <em>exception::what()</em> gelöst. Java und C# bieten jeweils noch eine Antwort auf die Frage &quot;wo ist es passiert?&quot; anhand eines Stack Traces. C++ bietet so etwas nicht eingebaut, aber man kann dennoch an einen Stack Trace gelangen.</p>
<p>Die einfachste Möglichkeit einen Stack Trace zu bekommen ist, einen Debugger mitlaufen zu lassen - in der Debug-Version, während wir noch testen, werden wir das vermutlich sowieso immer machen. Aber wenn wir keinen Debugger mitlaufen lassen haben, können wir die <a href="http://msdn.microsoft.com/en-us/library/ms680650.aspx" rel="nofollow">System API</a> verwenden (sofern wir mit Debug-Informationen kompiliert haben) oder aber eine <a href="http://www.codeproject.com/KB/cpp/exception.aspx" rel="nofollow">fertige Lösung</a>.</p>
<p>Das wichtigste Feature, das Exceptionklassen bieten müssen, ist eine durchdachte Hierachie. Denn wenn jeder Fehler, der erzeugt wird, lediglich vom Typ <code>StandardException</code> ist, kann man nur sehr schwer darauf reagieren. Es ist wichtig, einen Mittelweg aus zu tiefer Hierachie und zu breiter Hierachie zu finden. Denn wenn wir eine Exception von einer anderen erben lassen, muss dies wirklich eine &quot;A ist spezialfall von B&quot;-Situation sein. Oft ist so eine Entscheidung nicht leicht zu treffen: wenn ich eine Datei nicht öffnen kann, weil mir die Rechte fehlen, ist das dann eine <code>IOException</code> oder eine <code>SecurityException</code> ?</p>
<p>Der Konstruktor einer Exceptionklasse darf nie eine Exception erzeugen - denn wir wissen ja: sollte eine Exception auftreten, während eine Exception behandelt wird, wird das Programm beendet. Das bedeutet auch, dass man mit Speicherreservierungen vorsichtig sein muss.</p>
<h2>Exceptionsicherheit ohne try/catch</h2>
<p>Der große Vorteil von C++-Exceptions ist RAII. Anstatt überall try/catch schreiben zu müssen, können wir mit RAII die Fehlerfälle meistens sehr gut abfangen, ohne sie explizit zu behandeln.</p>
<p>Betrachten wir folgenden Code:</p>
<pre><code class="language-cpp">class Class {
private:
  char* name;
  int* array;

public:
  Class(char const* name)
  : name(new char[strlen(name)+1]), array(new int[100])
  {
    strcpy(this-&gt;name, name);
    fill(array);
  }
//...
};
</code></pre>
<p>Das Problem ist offensichtlich: wenn eine der beiden Allokationen fehlschlägt, wird die andere nicht mehr rückgängig gemacht. Wir könnten also mit try/catch versuchen, das Problem zu lösen:</p>
<pre><code class="language-cpp">class Class {
private:
  char* name;
  int* array;

public:
  Class(char const* name)
  : name(0), array(0)
  {
    try {
      this-&gt;name = new char[strlen(name)+1];
      array = new int[100];
    }
    catch(std::bad_alloc&amp; e) {
      delete [] this-&gt;name;
      delete [] array;
      throw;
    }
    strcpy(this-&gt;name, name);
    fill(array);
  }
//...
};
</code></pre>
<p>Das funktioniert zwar, aber es geht besser:</p>
<pre><code class="language-cpp">class Class {
private:
  std::string name;
  std::vector&lt;int&gt; array;

public:
  Class(char const* name)
  : name(name), array(100)
  {
    fill(array);
  }
//...
};
</code></pre>
<p>Nicht nur, dass wir jetzt keine Memory-Leaks mehr haben, wir haben auch noch den Code reduziert und schlanker gemacht. Meistens ist es eine gute Idee, dynamische Allokationen in eine Ressourcen-Klasse zu stecken, da so nicht nur Fehler verhindert werden, sondern der Code auch deutlich einfacher gestaltet bleibt.</p>
<p>Besonders problematisch sind dynamische Allokationen in einem Funktionsaufruf:</p>
<pre><code class="language-cpp">foo(new Bar(), new Baz());
</code></pre>
<p>Mit Smart Pointern wie z.B. scoped_ptr/auto_ptr oder shared_ptr kann man diese Probleme umgehen, indem die Smart Pointer die Ressource verwalten.</p>
<h2>Die weite Welt</h2>
<p>Exception Handling ist nur eine mögliche Lösung für das komplexe Problem der Fehlerbehandlung. Sie haben in diesem Artikel bereits ein paar Methoden kennengelernt, es gibt aber noch weit mehr. Jede dieser Methoden hat Vorteile und Nachteile, es gibt keine beste Lösung hier.</p>
<h3>Error Stack</h3>
<p>Jeder Fehler, der auftritt, wird auf einen bestimmten Stack gesetzt und die Funktion beendet sich selbst. An bestimmten Codestellen kann man dann auf Fehler testen, die ja alle auf diesem Error Stack liegen. Jeder Code kann einen behandelten Fehler vom Stack poppen - man hat somit ein feineres System was Fehlerbehandlung betrifft, als wir bei Exceptions haben (wo es nur den Zustand Fehler (Exception wurde geworfen) und nicht Fehler (keine Exception geworfen) gibt.</p>
<h3>Deferred Error Handling</h3>
<p><code>iostream</code> macht es vor: wenn ein Fehler auftritt, dann setzen wir ein internes error-flag und teilen so mit, dass etwas schiefgegangen ist.</p>
<h3>Callbacks</h3>
<p>Unter Unix sind Signals recht bekannt, in der Windows-Welt eher nicht. Dennoch bieten Signale eine interessante Möglichkeit Fehler zu handhaben. Jedes Mal wenn ein Fehler auftritt, wird ein Signal generiert, auf das eine Anwendung (oder ein Teil einer Anwendung) per Callback reagieren kann, indem man das Callback für das entsprechende Signal registriert.</p>
<p>Zu diesen 3 Methoden gibt es unter <a href="http://www.vollmann.ch/en/pubs/cpp-excpt-alt.html" rel="nofollow">C++ Exception Alternatives</a> auch ein bisschen Lesestoff, wenn Sie mehr erfahren wollen.</p>
<h3>Conditions</h3>
<p>Nicht jeder Fehler ist ein fataler Fehler. <a href="http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html" rel="nofollow">Conditions</a> ermöglichen es, an definierten Stellen von einem Fehler zu recovern.</p>
<p>Fehler passieren und egal, was wir für eine Methode verwenden, um sie zu handhaben, wir müssen achtgeben.</p>
]]></description><link>https://www.c-plusplus.net/forum/topic/219865/modernes-exception-handling-teil-2-hinter-den-kulissen</link><generator>RSS for Node</generator><lastBuildDate>Sun, 08 Mar 2026 06:59:11 GMT</lastBuildDate><atom:link href="https://www.c-plusplus.net/forum/topic/219865.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 08 Aug 2008 17:07:34 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Wed, 03 Sep 2008 07:10:43 GMT]]></title><description><![CDATA[<h1>Modernes Exception-Handling Teil 2 - Hinter den Kulissen</h1>
<p>Betrachten wir einmal, was hinter den Kulissen vor sich geht. Wie implementieren Compiler diesen Exception-Mechanismus? Als Beispiel nehmen wir den VC++ 2005 heran. Mit VC++ 2008 hat sich in Bezug auf Exceptionhandling nichts Grundlegendes verändert, es wurden nur ein paar kleine Optimierungen eingebaut - die Struktur blieb aber gleich.</p>
<p>Sehen wir uns folgenden einfachen Code an:</p>
<pre><code class="language-cpp">class Class {};

void do_nothing() {
}

void might_throw() {
	if(rand() % 2) {
		throw SomeException();
	}
}

void two_objects() {
	Class objA;
	might_throw();
	Class objB;
	might_throw();
	Class objC;
}

void pass_through() {
	two_objects();
}

void foo() {
	try {
		pass_through();
	}
	catch(SomeException&amp; e) {
		do_nothing();
		throw;
	}
}

int main() {
	foo();
}
</code></pre>
<p>Der Compiler muss im Falle einer Exception genau wissen, was er zu tun hat - er muss deshalb irgendwo Informationen haben, wo das Programm steht und was es zu tun gibt. Unter Windows 32 Bit ist das ganze per einfach verketteter Liste gelöst, in der alle Funktionen stehen, die im Falle einer Exception etwas tun müssen. Sehen wir uns diese Liste einmal an:<br />
<img src="https://static.c-plusplus.net/magazine/images/exceptions/win32_exception_frame_data.png" alt="" class="img-responsive img-markdown" /></p>
<p><code>might_throw</code> wirft eine Exception, aber es gibt nichts zu tun. Keine Objekte, die zerstört werden müssen und keine catch-Blöcke. <code>two_objects</code> braucht dagegen einen Eintrag in der Unwind-Liste. Denn <code>two_objects</code> erstellt Objekte, die zerstört werden müssen, wenn eine Exception fliegt. <code>pass_through</code> hat keinen Eintrag in der Liste, denn egal was passiert, <code>pass_through</code> leitet die Exception nur weiter, ohne selbst zu reagieren. <code>foo</code> dagegen hat einen catch-Block und muss somit Code ausführen, wenn eine Exception fliegt. Der catch-Block enthält Code, um zu überprüfen, ob die Exception hier gefangen wird oder durchgelassen wird. Selbst wenn <code>foo</code> die Exception nicht fangen würde, müsste dieser Code dennoch ausgeführt werden. <code>main</code> ist wie <code>pass_through</code> recht langweilig. <code>mainCRTStartup</code> ist eine magische Funktion der C/C++-Runtime. Hier werden globale Variablen wie <code>errno</code> initialisiert, der Heap angelegt, <code>argc</code> / <code>argv</code> gefüllt, etc. und ebenfalls ein try-catch-Block um <code>main</code> gelegt.</p>
<p>Jedes Mal, wenn eine Funktion betreten wird, wird ein Eintrag in der Unwind-Liste gemacht. Da aber einige Funktionen keinen Code haben, der abhängig von Exceptions ist, werden diese Funktionen nicht in der Liste eingetragen. Dieser Eintrag kostet natürlich Zeit und Speicher, deshalb optimiert der Compiler wo er nur kann. &quot;Static Data&quot; enthält Daten zu den jeweiligen Funktionen, die den aktuellen Status angeben.</p>
<p>Die interessante Funktion ist <code>two_objects</code> . Sehen wir uns <code>two_objects</code> einmal so an, wie ein sehr naiver Compiler sie implementieren könnte:</p>
<pre><code class="language-cpp">void two_objects() {
	//Metadaten
	//Eintrag in Unwind Liste
	Function this = new Function();
	unwindList.add(&amp;this);
	this.status=0;

	//Objekte anlegen:
	Class* objA;
	Class* objB;
	Class* objC;

	//Class objA; - objekt initialisieren
	objA=new Class();
	this.status=1;

	might_throw();
	if(exception) goto cleanup;

	//Class objB; - objekt initialisieren
	objB=new Class();
	this.status=2;

	might_throw();
	if(exception) goto cleanup;

	//Class objC; - objekt initialisieren
	objC=new Class();
	this.status=3;

	//aufräumen
cleanup:
	if(this.status==3) {
		delete objC;
		this.status=2;
	}
	if(this.status==2) {
		delete objB;
		this.status=1;
	}
	if(this.status==1) {
		delete objA;
		this.status=0;
	}
	if(this.status==0) {
		unwindList.remove(&amp;this);
		delete this;
	}
}
</code></pre>
<p>Der Cleanup-Code wird aus dem &quot;Static Data&quot;-Block abgeleitet, auf den das aktuelle Unwind-Element zeigt. Diese Statusvariable immer auf dem Laufenden zu halten, kostet ebenfalls Zeit und wird deshalb nur dann gemacht, wenn es wirklich notwendig ist. <code>this.status=3;</code> wäre in dem obigen Code wegoptimierbar.</p>
<p>Unter Windows 64 Bit sieht das ganze schon etwas besser aus. Statt einer Unwind-Liste haben wir eine Unwind-Map und als Schlüssel verwenden wir den Wert des Instruction Pointers, IP, zu dem Zeitpunkt, als die Exception geworfen wurde.</p>
<p><img src="https://static.c-plusplus.net/magazine/images/exceptions/win64_exception_frame_data.png" alt="" class="img-responsive img-markdown" /></p>
<p>Der Vorteil hier liegt auf der Hand: wir haben keine teure Liste, die wir pro Funktionsaufruf ändern müssen, wir haben nur eine komplexe Map, die wir einmal erstellt haben. Über den IP kann man den aktuellen Status der Funktion (und vor allem auch die Funktion selbst, in der man sich gerade befindet) herausfinden. Der Nachteil liegt im höheren Aufwand, falls eine Exception geworfen wird. Da Exceptions aber nur fliegen, wenn sowieso etwas nicht mit rechten Dingen zugeht, ist es durchaus vertretbar - vor allem, wenn man dafür den Nicht-Exception-Pfad deutlich beschleunigen kann.</p>
<p>Das ganze soll nicht vor Exceptions abschrecken - sie sind zwar nicht gratis (aus Performance-Sicht), aber man darf nicht vergessen, dass ein <code>if()</code> oder gar ganze <code>switch()</code> -Orgien bei der if-then-else-Fehlerbehandlung auch nicht gerade wenig Zeit kosten.</p>
<p>Wenn nun eine Exception geworfen wird, kann die Funktion sich selbst dank der Statusinformationen unwinden - aber wie genau wird der passende catch-Handler gefunden? VC++ geht hier einen 2-Pass-Weg, es wird die Unwind-Liste also zweimal durchgegangen.</p>
<p>Beim 1. Pass wird ein passender Exceptionhandler gesucht. Wenn keiner gefunden wird, wird <code>terminate()</code> aufgerufen, welches <code>abort()</code> aufruft und das Programm beendet. Wenn aber einer gefunden wurde, dann wird der 2. Pass ausgeführt, in welchem das Unwinding beginnt.</p>
<h2>Exkurs: Exceptions in C</h2>
<p>Im vorherigen Abschnitt haben wir einen Blick hinter die Kulissen des Exceptionhandlings geworfen. Doch C bzw. C++ wären nicht C bzw. C++ wenn man das ganze nicht auch händisch implementieren könnte. Einige Exceptionimplementierungen basieren auf genau dem Prinzip, das Sie gleich kennenlernen werden. Vor allem im Embedded Bereich, wo öfters C++-Exceptions nicht verwendet werden können, setzt man oft C-Exceptions ein.</p>
<p>Der Trick hinter Exceptions in C sind die beiden Funktionen <code>setjmp()</code> und <code>longjmp()</code> , deshalb wird diese Art der Implementierung auch gerne <em>sjlj-Exceptions</em> genannt.</p>
<p><code>setjmp</code> sichert den aktuellen Kontext in einen sogenannten jump-Buffer. Der Kontext enthält unter anderem die auto-Variablen am Stack und die Registerwerte. <code>setjmp</code> liefert immer 0 als Ergebnis. Nun verwendet man <code>longjmp</code> um einen Kontext wiederherzustellen (einschließlich des Instruction Pointers), wir landen mit der Ausführung also wieder in der Zeile, in der wir <code>setjmp()</code> aufgerufen haben. <code>longjmp</code> geben wir aber einen bestimmten Integer-Wert mit und diesen liefert <code>setjmp</code> uns jetzt - so können wir zwischen den einzelnen Fällen unterscheiden.</p>
<p>Da das sehr theoretisch klingt, ein kleines Beispiel:</p>
<pre><code class="language-cpp">#include &lt;stdio.h&gt;
#include &lt;setjmp.h&gt;
#include &lt;assert.h&gt;

jmp_buf jbuf;

#define E_DIVBYZERO -1
#define E_NOCLEANDIV -2

int divide(int a, int b) {
	if(b==0) {
		longjmp(jbuf, E_DIVBYZERO);
	}
	if(a%b != 0) {
		longjmp(jbuf, E_NOCLEANDIV);
	}
	return a/b;
}

int main() {

	switch(setjmp(jbuf)) {
	case 0:
		{
			int a,b,c;
			puts(&quot;please input an integer&quot;);
			scanf(&quot;%d&quot;, &amp;a);
			puts(&quot;please input another integer&quot;);
			scanf(&quot;%d&quot;, &amp;b);

			c=divide(a, b);
			printf(&quot;%d divided by %d gives %d\n&quot;, a, b, c);
			return 0;
		}
	case E_DIVBYZERO:
		{
			fputs(&quot;The integers couldn't be divided, due to a division by zero error.\n&quot;, stderr);
			return -1;
		}
	case E_NOCLEANDIV:
		{
			fputs(&quot;The integers couldn't be divided without a remainder.\n&quot;, stderr);
			return -1;
		}
	default:
		assert(0);
	}
	assert(0);
}
</code></pre>
<p><code>divide()</code> dividiert 2 Integer-Werte und liefert einen Fehler, wenn der Divisor 0 ist oder die Division einen Rest ergibt. Sobald wir eine Fehlersituation in <code>divide</code> haben, springen wir mit <code>longjmp</code> in das switch in main. Dort wird das Ergebnis ausgewertet und der passende Case-Zweig angesprungen.</p>
<p>Ein Wort der Warnung ist hier aber angebracht: lesen Sie genau in ihrer Compilerdokumentation nach, wie sich <em>sjlj</em> in einem C++-Programm verhält. Denn in einem C++-Programm muss der Destruktor von Objekten am Stack ausgeführt werden (etwas das wir uns in C ja sparen können).</p>
<p>Einen etwas tieferen Einblick in sjlj-Exceptions bieten Ihnen <a href="http://www.on-time.com/ddj0011.htm" rel="nofollow">Tom Schotland und Peter Petersen</a>.</p>
<h2>Exception-Safety testen</h2>
<p>Wie können wir garantieren, dass unsere Klassen exceptionsicher sind? Wir können natürlich den Code stundenlang analysieren und irgendwann sagen: so, jetzt haben wir alle Situationen bedacht. Das ist aber unpraktisch und in der Software-Entwicklung lechzen wir nach Automatisierungen.</p>
<p>Unittest: eine komplette Automatisierung ist mir leider nicht bekannt, aber es gibt Techniken, die man in seine Unittests einbauen kann. Die Idee ist eine Funktion <code>mightThrow()</code> in jede Funktion zu packen, die eine Exception werfen darf. Einfach ist das ganze, wenn wir z.B. einen Container oder ähnliches testen wollen:</p>
<pre><code class="language-cpp">class ThrowTestClass {
private:
	int value;
public:
	TestClass(int value=0) 
	: value(value) {
		mightThrow();
	}

	TestClass(TestClass const&amp; other)
	: value(other.value) {
		mightThrow();
	}

	int operator=(TestClass const&amp; other) {
		this-&gt;value = other.value;
		mightThrow();
	}
	//...
};

int main() {
	std::vector&lt;ThrowTestClass&gt; vec;
	test(vec);
}
</code></pre>
<p>Die ganze Magie befindet sich in der Funktion <code>mightThrow</code> .</p>
<pre><code class="language-cpp">void mightThrow() {
	if(!throwCounter--) {
		throw ExceptionSafetyTestException();
	}
}
</code></pre>
<p>Wir nehmen eine globale Variable und reduzieren sie immer um 1, wenn <code>mighThrow</code> aufgerufen wird. Wenn <code>throwCounter</code> 0 erreicht hat, dann wird eine Exception geworfen. Idealerweise iteriert <code>mightThrow</code> dann durch alle Exceptions, die die Funktion werfen darf, meistens ist das aber zu viel des Guten und es reicht eine Standard-Exception zu werfen. Sehen wir uns dazu jetzt die Testfunktion an:</p>
<pre><code class="language-cpp">template&lt;class Data, class Test&gt;
void basicGuaranteeCheck(Data&amp; data, Test const&amp; test) {
	bool finished=false;
	for(int nextThrowCounter=0; !finished; ++nextThrowCounter) {
		Data copy(data);
		throwCounter = nextThrowCounter;
		try {
			test.test(copy);
			finished=true;
		} catch(ExceptionSafetyTestException&amp; e) {
			//nothing
		}
		invariants(copy);
	}
}

template&lt;class Data, class Test&gt;
void strongGuaranteeCheck(Data&amp; data, Test const&amp; test) {
	bool finished=false;
	for(int nextThrowCounter=0; !finished; ++nextThrowCounter) {
		Data copy(data);
		throwCounter = nextThrowCounter;
		try {
			test.test(copy);
			finished=true;
		} catch(ExceptionSafetyTestException&amp; e) {
			REQUIRE(copy == data);
		}
		invariants(copy);
	}
}
</code></pre>
<p>Mit Hilfe von Regular Expressions lässt sich die Dokumentation des Codes dazu nutzen, die notwendigen throws zu generieren. Dabei wird auf einer Kopie des originalen Source Codes gearbeitet und am Anfang jeder Funktion, die Exceptions werfen darf, ein <code>mightThrow()</code> eingefügt.</p>
<p>Leider kenne ich keine Unittest-Library, die das unterstützt - aber vielleicht regt dieser Artikel ja den einen oder anderen an, so etwas in bestehende Librarys reinzupatchen.</p>
<p>Der Code der Testfunktion sollte leicht verständlich sein, deshalb sehen wir ihn uns nur kurz näher an. <code>data</code> ist ein Datenobjekt, z.B. ein Objekt einer Klasse, und <code>test</code> ist ein Objekt, das den Test ausführt. So könnte <code>data</code> z.B. ein <code>std::vector</code> sein und <code>test</code> könnte den operator= testen. Der throwCounter ist eine globale Variable, die bestimmt, wann mightThrow eine Exception wirft und anhand <code>finished</code> erkennen wir, wann keine Exception mehr geworfen wurde (und deshalb der Test beendet ist). Wir arbeiten dabei die ganze Zeit nur auf einer Kopie der echten Daten, da wir ja (zumindest bei der Strong-Garantie) testen wollen, ob der Zustand trotz Exception identisch geblieben ist. Mit <code>invariants()</code> überprüfen wir zum Schluss, ob die Invarianten noch alle stimmen.</p>
<h2>Interoperability von Exceptions</h2>
<p>In C++ leiden wir unter dem Fehlen eines <a href="http://en.wikipedia.org/wiki/Application_binary_interface" rel="nofollow">ABI</a>-Standards. Wir können leider nicht garantieren, dass eine Exception, die ein Binary (z.B. eine DLL oder SO) verlässt kompatibel mit den Exceptions in dem Binary ist, dass die Exception fängt. Natürlich ist es möglich, diese Kompatibilität zu erzwingen und in einigen Situationen macht das auch durchaus Sinn, aber wir sollten nicht davon ausgehen, dass dies immer zutrifft. Wir haben in C++ also das Problem, dass wir Exceptions nicht über Binary-Grenzen hinweg werfen dürfen. Wir müssen in solchen Situationen zu dem alten if-then-Error-Handling zurückkehren.</p>
<p>Java und .NET haben dieses Problem nicht, da sie jeweils ein standardisiertes ABI haben und daher das Werfen und Fangen von Exceptions über Binary-Grenzen hinweg kein Problem darstellt.</p>
<h2>Exceptionsicheres Klassendesign</h2>
<p>Nach welchen Richtlinien schreibt man denn nun exceptionsichere Klassen? Das Paradebeispiel dafür ist eine Stack-Klasse wie <code>std::stack</code> - wobei <code>std::stack</code> ja eigentlich nur ein <a href="http://de.wikipedia.org/wiki/Adapter_(Entwurfsmuster)" rel="nofollow">Container-Adapter</a> ist. Eine naive Implementierung einer Stack-Klasse könnte so aussehen:</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class Stack {
private:
	T* data;
	std::size_t used;
	std::size_t space;

public:
	explicit Stack(std::size_t expectedElements = 100)
	: data(static_cast&lt;T*&gt;(operator new(expectedElements*sizeof T)))
	, used(0)
	, space(expectedElements) {
	}

	Stack(Stack const&amp; other)
	: data(static_cast&lt;T*&gt;(operator new(other.used*sizeof T)))
	, used(other.used)
	, space(other.used) {
		std::uninitialized_copy(other.data, other.data+other.used, data);
	}

	~Stack() {
		std::destroy(data, data+used);
		operator delete(data);
	}

	Stack&amp; operator=(Stack&amp; const other) {
		Stack temp(other);
		swap(temp);
		return *this;
	}

	void push(T const&amp; obj) {
		if(space&gt;used) {
			std::consruct(data+used, obj);
			++used;
			return;
		}

		space*=2+1;
		T* temp=operator new(space*sizeof T);
		std::uninitialized_copy(data, data+used, temp);
		std::construct(temp+used, obj);
		std::swap(data, temp);
		std::destroy(temp, temp+used);
		operator delete(temp);
		++used;
	}

	T pop() {
		if(empty())
			throw StackEmptyException();
		T temp(data[--used]);
		std::destroy(data+used);
		return temp;
	}

	bool empty() const {
		return used==0;
	}

	std::size_t size() const {
		return used;
	}

	void swap(Stack&amp; other) {
		std::swap(data, other.data);
		std::swap(used, other.used);
		std::swap(space, other.space);
	}
};
</code></pre>
<p>Hier gibt es eine Menge Probleme. Gehen wir sie der Reihe nach an:</p>
<pre><code class="language-cpp">Stack(Stack const&amp; other)
	: data(static_cast&lt;T*&gt;(operator new(other.used*sizeof T)))
	, used(other.used)
	, space(other.used) {
		std::uninitialized_copy(other.data, other.data+other.used, data);
	}
</code></pre>
<p>Sollte eine Kopieroperation in <code>std::uninitialized_copy</code> fehlschlagen, so wird der Speicher, auf den data zeigt, nicht aufgeräumt.</p>
<pre><code class="language-cpp">void push(T const&amp; obj) {
		if(space&gt;used) {
			std::consruct(data+used, obj);
			++used;
			return;
		}

		space*=2+1;
		T* temp=operator new(space*sizeof T);
		std::uninitialized_copy(data, data+used, temp);
		std::construct(temp+used, obj);
		std::swap(data, temp);
		std::destroy(temp, temp+used);
		operator delete(temp);
		++used;
	}
</code></pre>
<p><code>space</code> wird erhöht, bevor die Kopieroperationen beendet sind. Sollte <code>new</code> oder <code>std::copy()</code> fehlschlagen, bleibt <code>space</code> auf dem erhöhten Wert, obwohl keine Erhöhung stattfand.</p>
<pre><code class="language-cpp">T pop() {
		if(empty())
			throw StackEmptyException();
		T temp(data[--used]);
		std::destroy(data+used);
		return temp;
	}
</code></pre>
<p>Sollte eine der beiden Kopieroperation fehlschlagen, geht das Objekt für immer verloren, da wir es bereits aus unserem Stack gelöscht haben - es aber nie beim Caller ankam.</p>
<p>Eine elegantere Variante diese Probleme zu umgehen, wäre folgende Implementierung:</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class StackImpl {
public:
	T* data;
	std::size_t used;
	std::size_t space;

	explicit StackImpl(std::size_t elements)
	: data(static_cast&lt;T*&gt;(operator new(elements*sizeof T)))
	, used(0)
	, space(elements) {
	}

	~StackImpl() {
		std::destroy(data, data+used);
		operator delete(data);
	}

	void swap(StackImpl&amp; other) {
		std::swap(data, other.data);
		std::swap(used, other.used);
		std::swap(space, other.space);
	}

private:
	StackImpl(StackImpl const&amp;);
	StackImpl&amp; operator=(StackImpl&amp; const);

};

template&lt;typename T&gt;
class Stack {
private:
	StackImpl&lt;T&gt; impl;

public:
	explicit Stack(std::size_t expectedElements = 100)
	: impl(expectedElements) {
	}

	Stack(Stack const&amp; other)
	: impl(other.impl.used) {
		std::uninitialized_copy(other.impl.data, other.impl.data+other.impl.used, impl.data);
		impl.used=other.impl.used;
	}

	Stack&amp; operator=(Stack&amp; const other) {
		Stack temp(other);
		swap(temp);
		return *this;
	}

	void push(T const&amp; obj) {
		if(impl.space == impl.used) {
			Stack temp(impl.space*2+1);
			std::unitialized_copy(impl.data, impl.data+impl.used, temp.impl.data);
			temp.impl.used=impl.used;
			swap(temp);
		}
		std::construct(impl.data+impl.used, obj);
		++impl.used;
	}

	void pop() {
		if(empty())
			throw StackEmptyException();
		std::destroy(impl.data+impl.used-1);
		--impl.used;
	}

	T&amp; top() {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}

	T const&amp; top() const {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}

	bool empty() const {
		return impl.used==0;
	}

	std::size_t size() const {
		return impl.used;
	}

	void swap(Stack&amp; other) {
		impl.swap(other.impl);
	}
};
</code></pre>
<p>Da wir eine Hilfsklasse verwenden, die das Speichermanagement übernimmt, entstehen im Konstruktor keine Speicherlecks mehr:</p>
<pre><code class="language-cpp">Stack(Stack const&amp; other)
	: impl(other.impl.used) {
		std::uninitialized_copy(other.impl.data, other.impl.data+other.impl.used, impl.data);
		impl.used=other.impl.used;
	}
</code></pre>
<p>Sollte <code>std::uninitialized_copy</code> fehlschlagen, wird dennoch <code>impl</code> zerstört und der Speicher korrekt freigegeben. Wichtig ist, dass <code>used</code> erst gesetzt wird, nachdem das Kopieren erfolgreich war.</p>
<pre><code class="language-cpp">void push(T const&amp; obj) {
		if(impl.space == impl.used) {
			Stack temp(impl.space*2+1);
			std::unitialized_copy(impl.data, impl.data+impl.used, temp.impl.data);
			temp.impl.used=impl.used;
			swap(temp);
		}
		std::construct(impl.data+impl.used, obj);
		++impl.used;
	}
</code></pre>
<p>Wir verwenden hier das bekannte Copy&amp;Swap, um den Speicherbereich zu vergrößern. Wir reduzieren dadurch den nötigen Code und gewinnen Robustheit.</p>
<pre><code class="language-cpp">void pop() {
		if(empty())
			throw StackEmptyException();
		std::destroy(impl.data+impl.used-1);
		--impl.used;
	}

	T&amp; top() {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}

	T const&amp; top() const {
		if(empty())
			throw StackEmptyException();
		return impl.data[impl.used-1];
	}
</code></pre>
<p>Ein <code>pop()</code> , das den gepopten Wert by Value liefert, kann nie exceptionsicher sein. Wir brauchen daher eine <code>top()</code> -Methode, um an das oberste Element zu gelangen. Nebenbei gewinnen wir dadurch noch die Möglichkeit Stack als ein konstantes Objekt verwenden zu können, da wir nun mit <code>top()</code> an das oberste Element kommen, ohne den Stack ändern zu müssen.</p>
<h2>Design von Exceptionklassen</h2>
<p>Je nachdem mit welcher Sprache man arbeitet, sehen Exceptions immer leicht anders aus. Exceptions können das Debuggen erleichtern, wenn sie wichtige Informationen wie <em>Was ist passiert?</em>, <em>Wo ist es passiert?</em> und u.U. auch ein <em>Warum ist es passiert?</em> mitteilen. Das essentiellste davon ist &quot;Was ist passiert?&quot;. In der C++-Standard-Library über die virtuelle Funktion <em>exception::what()</em> gelöst. Java und C# bieten jeweils noch eine Antwort auf die Frage &quot;wo ist es passiert?&quot; anhand eines Stack Traces. C++ bietet so etwas nicht eingebaut, aber man kann dennoch an einen Stack Trace gelangen.</p>
<p>Die einfachste Möglichkeit einen Stack Trace zu bekommen ist, einen Debugger mitlaufen zu lassen - in der Debug-Version, während wir noch testen, werden wir das vermutlich sowieso immer machen. Aber wenn wir keinen Debugger mitlaufen lassen haben, können wir die <a href="http://msdn.microsoft.com/en-us/library/ms680650.aspx" rel="nofollow">System API</a> verwenden (sofern wir mit Debug-Informationen kompiliert haben) oder aber eine <a href="http://www.codeproject.com/KB/cpp/exception.aspx" rel="nofollow">fertige Lösung</a>.</p>
<p>Das wichtigste Feature, das Exceptionklassen bieten müssen, ist eine durchdachte Hierachie. Denn wenn jeder Fehler, der erzeugt wird, lediglich vom Typ <code>StandardException</code> ist, kann man nur sehr schwer darauf reagieren. Es ist wichtig, einen Mittelweg aus zu tiefer Hierachie und zu breiter Hierachie zu finden. Denn wenn wir eine Exception von einer anderen erben lassen, muss dies wirklich eine &quot;A ist spezialfall von B&quot;-Situation sein. Oft ist so eine Entscheidung nicht leicht zu treffen: wenn ich eine Datei nicht öffnen kann, weil mir die Rechte fehlen, ist das dann eine <code>IOException</code> oder eine <code>SecurityException</code> ?</p>
<p>Der Konstruktor einer Exceptionklasse darf nie eine Exception erzeugen - denn wir wissen ja: sollte eine Exception auftreten, während eine Exception behandelt wird, wird das Programm beendet. Das bedeutet auch, dass man mit Speicherreservierungen vorsichtig sein muss.</p>
<h2>Exceptionsicherheit ohne try/catch</h2>
<p>Der große Vorteil von C++-Exceptions ist RAII. Anstatt überall try/catch schreiben zu müssen, können wir mit RAII die Fehlerfälle meistens sehr gut abfangen, ohne sie explizit zu behandeln.</p>
<p>Betrachten wir folgenden Code:</p>
<pre><code class="language-cpp">class Class {
private:
  char* name;
  int* array;

public:
  Class(char const* name)
  : name(new char[strlen(name)+1]), array(new int[100])
  {
    strcpy(this-&gt;name, name);
    fill(array);
  }
//...
};
</code></pre>
<p>Das Problem ist offensichtlich: wenn eine der beiden Allokationen fehlschlägt, wird die andere nicht mehr rückgängig gemacht. Wir könnten also mit try/catch versuchen, das Problem zu lösen:</p>
<pre><code class="language-cpp">class Class {
private:
  char* name;
  int* array;

public:
  Class(char const* name)
  : name(0), array(0)
  {
    try {
      this-&gt;name = new char[strlen(name)+1];
      array = new int[100];
    }
    catch(std::bad_alloc&amp; e) {
      delete [] this-&gt;name;
      delete [] array;
      throw;
    }
    strcpy(this-&gt;name, name);
    fill(array);
  }
//...
};
</code></pre>
<p>Das funktioniert zwar, aber es geht besser:</p>
<pre><code class="language-cpp">class Class {
private:
  std::string name;
  std::vector&lt;int&gt; array;

public:
  Class(char const* name)
  : name(name), array(100)
  {
    fill(array);
  }
//...
};
</code></pre>
<p>Nicht nur, dass wir jetzt keine Memory-Leaks mehr haben, wir haben auch noch den Code reduziert und schlanker gemacht. Meistens ist es eine gute Idee, dynamische Allokationen in eine Ressourcen-Klasse zu stecken, da so nicht nur Fehler verhindert werden, sondern der Code auch deutlich einfacher gestaltet bleibt.</p>
<p>Besonders problematisch sind dynamische Allokationen in einem Funktionsaufruf:</p>
<pre><code class="language-cpp">foo(new Bar(), new Baz());
</code></pre>
<p>Mit Smart Pointern wie z.B. scoped_ptr/auto_ptr oder shared_ptr kann man diese Probleme umgehen, indem die Smart Pointer die Ressource verwalten.</p>
<h2>Die weite Welt</h2>
<p>Exception Handling ist nur eine mögliche Lösung für das komplexe Problem der Fehlerbehandlung. Sie haben in diesem Artikel bereits ein paar Methoden kennengelernt, es gibt aber noch weit mehr. Jede dieser Methoden hat Vorteile und Nachteile, es gibt keine beste Lösung hier.</p>
<h3>Error Stack</h3>
<p>Jeder Fehler, der auftritt, wird auf einen bestimmten Stack gesetzt und die Funktion beendet sich selbst. An bestimmten Codestellen kann man dann auf Fehler testen, die ja alle auf diesem Error Stack liegen. Jeder Code kann einen behandelten Fehler vom Stack poppen - man hat somit ein feineres System was Fehlerbehandlung betrifft, als wir bei Exceptions haben (wo es nur den Zustand Fehler (Exception wurde geworfen) und nicht Fehler (keine Exception geworfen) gibt.</p>
<h3>Deferred Error Handling</h3>
<p><code>iostream</code> macht es vor: wenn ein Fehler auftritt, dann setzen wir ein internes error-flag und teilen so mit, dass etwas schiefgegangen ist.</p>
<h3>Callbacks</h3>
<p>Unter Unix sind Signals recht bekannt, in der Windows-Welt eher nicht. Dennoch bieten Signale eine interessante Möglichkeit Fehler zu handhaben. Jedes Mal wenn ein Fehler auftritt, wird ein Signal generiert, auf das eine Anwendung (oder ein Teil einer Anwendung) per Callback reagieren kann, indem man das Callback für das entsprechende Signal registriert.</p>
<p>Zu diesen 3 Methoden gibt es unter <a href="http://www.vollmann.ch/en/pubs/cpp-excpt-alt.html" rel="nofollow">C++ Exception Alternatives</a> auch ein bisschen Lesestoff, wenn Sie mehr erfahren wollen.</p>
<h3>Conditions</h3>
<p>Nicht jeder Fehler ist ein fataler Fehler. <a href="http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html" rel="nofollow">Conditions</a> ermöglichen es, an definierten Stellen von einem Fehler zu recovern.</p>
<p>Fehler passieren und egal, was wir für eine Methode verwenden, um sie zu handhaben, wir müssen achtgeben.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/1561584</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/1561584</guid><dc:creator><![CDATA[Shade Of Mine]]></dc:creator><pubDate>Wed, 03 Sep 2008 07:10:43 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Thu, 14 Aug 2008 18:26:41 GMT]]></title><description><![CDATA[<p>Hoho harter Tobak, gut geschrieben. Danke für die Infos! <img
      src="https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f44d.png?v=ab1pehoraso"
      class="not-responsive emoji emoji-emoji-one emoji--thumbs_up"
      title=":+1:"
      alt="👍"
    /></p>
]]></description><link>https://www.c-plusplus.net/forum/post/1564965</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/1564965</guid><dc:creator><![CDATA[Pellaeon]]></dc:creator><pubDate>Thu, 14 Aug 2008 18:26:41 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Thu, 14 Aug 2008 21:03:31 GMT]]></title><description><![CDATA[<p><img
      src="https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f44d.png?v=ab1pehoraso"
      class="not-responsive emoji emoji-emoji-one emoji--thumbs_up"
      title=":+1:"
      alt="👍"
    /><br />
Sehr schön. Gefällt mir. <img
      src="https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f642.png?v=ab1pehoraso"
      class="not-responsive emoji emoji-emoji-one emoji--slightly_smiling_face"
      title=":)"
      alt="🙂"
    /></p>
]]></description><link>https://www.c-plusplus.net/forum/post/1565042</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/1565042</guid><dc:creator><![CDATA[drakon]]></dc:creator><pubDate>Thu, 14 Aug 2008 21:03:31 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 14 Sep 2008 13:11:42 GMT]]></title><description><![CDATA[<p>Ziemlich verständlich und verdammt interessant! Danke dir für den Blick hinter den Vorhang! <img
      src="https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f642.png?v=ab1pehoraso"
      class="not-responsive emoji emoji-emoji-one emoji--slightly_smiling_face"
      title=":)"
      alt="🙂"
    /></p>
]]></description><link>https://www.c-plusplus.net/forum/post/1581715</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/1581715</guid><dc:creator><![CDATA[Badestrand]]></dc:creator><pubDate>Sun, 14 Sep 2008 13:11:42 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 14 Sep 2008 14:18:55 GMT]]></title><description><![CDATA[<p>Ein paar kleine Anmerkungen:</p>
<p>Ich kenne kein std::construct oder std::destroy. Als Referenz könnte man diese Implementation zu benutzen:</p>
<pre><code class="language-cpp">template &lt;typename T&gt;
void construct(T* where, const T&amp; value)
{
    new (static_cast&lt;void*&gt;(where)) T(value);
}

template &lt;typename T&gt;
void destroy(T* where)
{
    where-&gt;~T();
}

template &lt;typename BidirectionalIterator&gt;
void destroy(BidirectionalIterator first, BidirectionalIterator last)
{
    typedef typename std::iterator_traits&lt; BidirectionalIterator &gt;::value_type value_type;
    for ( ; last != first; )
        (--last)-&gt;~value_type();
}
</code></pre>
<p>sizeof benötigt Klammern, wenn der Operand ein Typ ist. Fehlplatziertes const im Zuweisungsoperator.</p>
<p>Die Verteilung der Aufgaben auf Stack und StackImpl ist verwirrend, da Stack ist für die Erzeugung und StackImpl für die Zerstörung der Objekte zuständig ist. Sinnvoller und einfacher wäre die Verwendung einer einfachen RAII-Klasse, die nur den rohen Speicher als solchen verwaltet. Tatsächlich bietet sich hier ein Smartpointer an - wenn man will, z.B. auch shared_array mit custom-deleter. Dann muss man auch nicht in den Innereien anderer lassen herumpfuschen.</p>
<blockquote>
<p>Ein pop(), das den gepopten Wert by Value liefert, kann nie exceptionsicher sein.</p>
</blockquote>
<p>Hier wage ich zu widersprechen. Es ist lediglich nicht ganz trivial (und grundsätzlich bin ich auch für die AUfteilung in top und pop - denn je nachdem, wie man ein pop mit return implementiert, hat man nur zwei schlechte Alternativen:<br />
1. wenn das Element trotz Exception entfernt wird, verliert man unwiederbringlich Daten</p>
<pre><code class="language-cpp">T pop()
{
    if(empty())
        throw StackEmptyException();
    struct on_exit
    {
        stack&amp; s;
        on_exit(stack&amp; s) : s(s) {}
        ~on_exit() { destroy(s.impl.data+--s.impl.used); }
    } on_exit_guard( *this );
    return impl.data[impl.used-1];
}
</code></pre>
<p>2. wenn der Stack bei Exception unverändert bleibt, kann man ihn ggf. überhaupt nicht mehr löschen.</p>
<pre><code class="language-cpp">T pop()
{
    if(empty())
        throw StackEmptyException();
    struct on_exit
    {
        stack&amp; s;
        bool do_pop;
        on_exit(stack&amp; s) : s(s), do_pop(true) {}
        ~on_exit() { if (do_pop) destroy(s.impl.data+--s.impl.used); }
        void dismiss() { do_pop=false; }
    } on_exit_guard( *this );
    try
    {
        return impl.data[impl.used-1];
    }
    catch ( ... )
    {
        on_exit_guard.dismiss();
        throw;
    }
}
</code></pre>
]]></description><link>https://www.c-plusplus.net/forum/post/1581751</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/1581751</guid><dc:creator><![CDATA[camper]]></dc:creator><pubDate>Sun, 14 Sep 2008 14:18:55 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 14 Sep 2008 18:10:25 GMT]]></title><description><![CDATA[<p>camper schrieb:</p>
<blockquote>
<p>Die Verteilung der Aufgaben auf Stack und StackImpl ist verwirrend, da Stack ist für die Erzeugung und StackImpl für die Zerstörung der Objekte zuständig ist.</p>
</blockquote>
<p>Ist eine gaengige Implementierunsvariante. irgendwas ala shared_X ist einfach nur falsch hier zu verwenden. ein scoped array uU, aber wozu? eine klasse muss nicht immer eine vollstaendig allein funktionierende einheit sein. stackimpl ist nur ein helfer der sich um den speicher kuemmert.</p>
<p>impl ist eine RAII klasse, nur eben keine selbststaendige. aber wozu muss sie eine vollstaendige eigenstaendige klasse sein? welchen vorteil habe ich davon? mehr aufwand. und wiederverwenden kann ich es eh nicht.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/1581870</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/1581870</guid><dc:creator><![CDATA[Shade Of Mine]]></dc:creator><pubDate>Sun, 14 Sep 2008 18:10:25 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sat, 03 Feb 2024 14:04:57 GMT]]></title><description><![CDATA[<p>Frage zum Artikel, da steht, daß man nicht den ganzen Code in einen try-Block setzen soll. Warum nicht?</p>
<p>MFG</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620238</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620238</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Sat, 03 Feb 2024 14:04:57 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sat, 03 Feb 2024 17:07:02 GMT]]></title><description><![CDATA[<p>Weil du dann nicht weißt woher und warum die Exception geflogen ist, könnte quasi ja von überall herkommen. Ist aber oft als &quot;last resort&quot; ganz außen in Programmen üblich.</p>
<p>MfG SideWinder</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620240</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620240</guid><dc:creator><![CDATA[SideWinder]]></dc:creator><pubDate>Sat, 03 Feb 2024 17:07:02 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sat, 03 Feb 2024 17:14:32 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/201">@SideWinder</a> sagte in <a href="/forum/post/2620240">Modernes Exception-Handling Teil 2 - Hinter den Kulissen</a>:</p>
<blockquote>
<p>Weil du dann nicht weißt woher und warum die Exception geflogen ist, könnte quasi ja von überall herkommen. Ist aber oft als &quot;last resort&quot; ganz außen in Programmen üblich.</p>
<p>MfG SideWinder</p>
</blockquote>
<p>Im Artikel steht aber auch daß man Exceptions zentral auffangen soll. Das beißt sich schon ein bischen, meinen Sie nicht auch?</p>
<p>Denn: Eine zentrale Fehlerbehandlung funktioniert ja nur wenn die Exceptions entsprechend klassifiziert sind. Und genau damit weiß man auch wo sie gefallen sind. Noch dazu wenn ein Backtrace verfügbar ist.</p>
<p>MFG</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620241</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620241</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Sat, 03 Feb 2024 17:14:32 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sat, 03 Feb 2024 20:40:53 GMT]]></title><description><![CDATA[<p>Beides ist imho richtig: fange kontextlose Exceptions möglichst lokal und kontextbehaftete dann zentral. Ich versuch's mal mit einem Beispiel:</p>
<pre><code class="language-csharp">try
{
    auto x = query_database_a(); // wirft network_exception wenn datenbank nicht erreichbar

    // wenn hier drin noch viele andere query_database_b(), etc. wären, die auch network_exception werfen, wüsste man unten im catch nicht welche datenbank überhaupt die war die nicht erreichbar war =&gt; daher möglichst kleiner try/catch-block
}
catch(network_exception&amp; ne)
{
    // network_exception ist kontextlos, aber weil ich try nur um ein statement rumgemacht habe, weiß ich sofort, dass es sich um ein problem bei query_database_a() handeln muss:
    throw database_a_error(&quot;Could not reach database A.&quot;); // diese exception hier kann man nun möglichst zentral catchen und einen error dialog anzeigen oder ähnliches
}
</code></pre>
<p>Hoffe das Beispile hilft. Ist zumindest ein Weg den ich in C#/Java immer gehe, kenne mich mit aktuellem C++ Exception Best Practices nicht aus.</p>
<p>MfG SideWinder</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620242</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620242</guid><dc:creator><![CDATA[SideWinder]]></dc:creator><pubDate>Sat, 03 Feb 2024 20:40:53 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 04 Feb 2024 08:02:35 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/201">@SideWinder</a></p>
<p>ich hatte mal ein Programm (Perl) das lief nicht auf jedem System. Selbst der Kollege der den Code entwickelt hatte wusste nicht warum, außer der Feststellung daß es wohl ein lokales Problem sein müsse.<br />
Kurzerhand setzte ich den ganzen Code in einen try-Block und fand damit raus woran es gelegen hat. Natürlich war es ein lokales Problem aber das war ja vorher schon klar wenns bei allen anderen Kunden läuft nur bei dem einem nicht.</p>
<p>MFG</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620252</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620252</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Sun, 04 Feb 2024 08:02:35 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 04 Feb 2024 09:26:25 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/201">@SideWinder</a></p>
<p>zu Deinem Code: Wenn eine <code>network_exception</code> von der Stange nicht unterscheidet zwischen einer nicht zustandegekommenen Verbindung und einer verlorenen Verbindung ist das ein Grund über eine eigene Klassifizierung nachzudenken, also eigene Exception-Klassen. darüber hinaus gibt es sicher auch in C# und Java Möglichkeiten vor einer Query den Verbindungsstatus abzufragen (ping).<br />
Auf diese Art und Weise muss man nicht jedes Statement in einen eigenen try-Block setzen sondern einfach nur eine gezielte Ex werfen die zentral gefangen wird.</p>
<p>MFG</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620253</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620253</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Sun, 04 Feb 2024 09:26:25 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 04 Feb 2024 11:40:09 GMT]]></title><description><![CDATA[<p>Auch wenn eine sehr gute Exception geworfen wird, wird sie evtl. nicht zwischen database_a und database_b unterscheiden wenn es zwei Connections sind die zurselben Art von DB gehen, aber sind natürlich alles konstruierte Beispiele, wenn die Exception gut genug ist, spricht natürlich absolut nichts dagegen den catch-Block so weit außen wie möglich zu machen.</p>
<p>MfG SideWinder</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620258</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620258</guid><dc:creator><![CDATA[SideWinder]]></dc:creator><pubDate>Sun, 04 Feb 2024 11:40:09 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 04 Feb 2024 12:25:15 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/201">@SideWinder</a></p>
<p>so ist es. Es kommt ja auch darauf an, was mit der Exception gemacht werden soll. Ich hatte z.B. mal ein Programm für einen Datentransfer von Oracle nach MySQL zu entwickeln, da kam es nur darauf an, daß dieses Programm bei nicht Erreichbarkeit einer der beiden (egal welche) in einer Warteschleife weiterläuft bis die Verbindung wieder steht.</p>
<p>Schönen Sonntag!</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620260</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620260</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Sun, 04 Feb 2024 12:25:15 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Sun, 04 Feb 2024 14:05:55 GMT]]></title><description><![CDATA[<p>Die Aussage ist wohl nur, wenn du innerhalb einer Methode einen try/catch-Block machst, dann ist es best practice ihn so klein wie möglich zu machen. Dass du weit außen ein einziges try/catch rund um app.Run() hast, ist davon nicht betroffen.</p>
<p>MfG SideWinder</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620261</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620261</guid><dc:creator><![CDATA[SideWinder]]></dc:creator><pubDate>Sun, 04 Feb 2024 14:05:55 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Mon, 05 Feb 2024 17:03:38 GMT]]></title><description><![CDATA[<p>Ich fahre da zwei Philosophien (je nach Projekt)... Entweder, alles &quot;nach oben&quot; weitergeben - oder aber die try-catch-Blöcke so früh und klein wie möglich wählen, damit man a weiß, was die Exception verursachte, und b ggf. darauf entsprechend regieren kann, ohne das Programm zu beenden.</p>
<p>Ein von <a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431">@_ro_ro</a> vorgeschlagener Mischmasch führt meines Erachtens nur zu Chaos.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620284</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620284</guid><dc:creator><![CDATA[omggg]]></dc:creator><pubDate>Mon, 05 Feb 2024 17:03:38 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Mon, 05 Feb 2024 17:04:12 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38453">@omggg</a> sagte in <a href="/forum/post/2620284">Modernes Exception-Handling Teil 2 - Hinter den Kulissen</a>:</p>
<blockquote>
<p>Ich fahre da zwei Philosophien (je nach Projekt)... Entweder, alles &quot;nach oben&quot; weitergeben - oder aber die try-catch-Blöcke so früh und klein wie möglich wählen, damit mit man a weiß, was die Exception verursachte, und b ggf. darauf entsprechend regieren kann, ohne das Programm zu beenden.</p>
<p>Ein von <a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431">@_ro_ro</a> vorgeschlagener Mischmasch für meines Erachtens nur zu Chaos.</p>
</blockquote>
<p>Ich habe gar nichts vorgeschlagen. Ich finde nur, daß der Artikel in sich widersprüchliche Aussagen trifft.</p>
<p>Im übrigen ist meine Frage immer noch nicht beantwortet. MFG</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620285</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620285</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Mon, 05 Feb 2024 17:04:12 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Mon, 05 Feb 2024 17:05:05 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431"><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431">@_ro_ro</a></a> sagte in <a href="/forum/post/2620285">Modernes Exception-Handling Teil 2 - Hinter den Kulissen</a>:</p>
<blockquote>
<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38453">@omggg</a> sagte in <a href="/forum/post/2620284">Modernes Exception-Handling Teil 2 - Hinter den Kulissen</a>:</p>
<blockquote>
<p>Ich fahre da zwei Philosophien (je nach Projekt)... Entweder, alles &quot;nach oben&quot; weitergeben - oder aber die try-catch-Blöcke so früh und klein wie möglich wählen, damit mit man a weiß, was die Exception verursachte, und b ggf. darauf entsprechend regieren kann, ohne das Programm zu beenden.</p>
<p>Ein von <a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431"><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431">@_ro_ro</a></a> vorgeschlagener Mischmasch für meines Erachtens nur zu Chaos.</p>
</blockquote>
<p>Ich habe gar nichts vorgeschlagen. Ich finde nur, daß der Artikel in sich widersprüchliche Aussagen trifft.</p>
<p>Im übrigen ist meine Frage immer noch nicht beantwortet. MFG</p>
</blockquote>
<p>Kein Grund, aggressiv zu werden oder zu eskalieren ... Ich habe nur den Subtext gedeutet.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620286</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620286</guid><dc:creator><![CDATA[omggg]]></dc:creator><pubDate>Mon, 05 Feb 2024 17:05:05 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Tue, 06 Feb 2024 08:28:01 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431">@_ro_ro</a> sagte in <a href="/forum/post/2620238">Modernes Exception-Handling Teil 2 - Hinter den Kulissen</a>:</p>
<blockquote>
<p>Frage zum Artikel, da steht, daß man nicht den ganzen Code in einen try-Block setzen soll. Warum nicht?</p>
</blockquote>
<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38431">@_ro_ro</a> sagte in <a href="/forum/post/2620285">Modernes Exception-Handling Teil 2 - Hinter den Kulissen</a>:</p>
<blockquote>
<p>Ich habe gar nichts vorgeschlagen. Ich finde nur, daß der Artikel in sich widersprüchliche Aussagen trifft.</p>
</blockquote>
<p>Welche Sätze meinst du denn genau? Zumindestens in diesem (Teil-)Artikel finde ich keine Aussagen bzgl. der Vermeidung eines globalen <code>try...catch</code>-Handlers.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620291</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620291</guid><dc:creator><![CDATA[Th69]]></dc:creator><pubDate>Tue, 06 Feb 2024 08:28:01 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Tue, 06 Feb 2024 10:58:15 GMT]]></title><description><![CDATA[<p>Da der Thread bzw der Artikel ursprünglich mal &quot;Modernes Exception Handling&quot; hieß; in modernem C++ gibt es auch andere Möglichkeiten der Fehlerbehandlung, z.B. mit <code>std::optional</code>oder noch neuer  <code>std::except</code>. Hier ein Artikel dazu: <a href="https://devblogs.microsoft.com/cppblog/cpp23s-optional-and-expected/" rel="nofollow">https://devblogs.microsoft.com/cppblog/cpp23s-optional-and-expected/</a></p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620292</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620292</guid><dc:creator><![CDATA[Schlangenmensch]]></dc:creator><pubDate>Tue, 06 Feb 2024 10:58:15 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Tue, 06 Feb 2024 12:05:00 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/20667">@Schlangenmensch</a></p>
<p>die Erfahrung daß man mit Exceptions seine Fehlerbehandlung vereinfachen kann teile ich auf jeden Fall. Darüber habe ich auch schon vor ein paar Jahren Artikel geschrieben.</p>
<p>MFG</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2620296</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2620296</guid><dc:creator><![CDATA[_ro_ro]]></dc:creator><pubDate>Tue, 06 Feb 2024 12:05:00 GMT</pubDate></item><item><title><![CDATA[Reply to Modernes Exception-Handling Teil 2 - Hinter den Kulissen on Fri, 19 Jul 2024 10:05:21 GMT]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="https://www.c-plusplus.net/forum/uid/38453">@omggg</a> Es gibt kein Modernes Exception Handling, da keine Ausnahmen stattfinden. Wenn doch ist was anderes unbrauchbar.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2622013</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2622013</guid><dc:creator><![CDATA[[[global:former_user]]]]></dc:creator><pubDate>Fri, 19 Jul 2024 10:05:21 GMT</pubDate></item></channel></rss>