-
Notifications
You must be signed in to change notification settings - Fork 1
/
exception_handling.hpp
286 lines (251 loc) · 7.69 KB
/
exception_handling.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#pragma once
#include <cassert>
#include <functional>
#include <exception>
#include <vector>
namespace cu
{
/// This function returns the chain of nested exceptions.
///
/// If an exception is thrown that inherits from @c std::nested_exception,
/// then the currently caught exception is stored in the
/// @c std::nested_exception subobject, if any. This caught exception
/// is said to be a nested exception. If this happens several times, then
/// there can be a whole chain of nested exceptions. This function returns
/// this chain of nested exceptions beginning with the passed exception
/// pointer, if it is not null.
inline std::vector<std::exception_ptr> getNestedExceptionPtrs(
std::exception_ptr e_ptr = std::current_exception() )
{
std::vector<std::exception_ptr> result{};
while ( e_ptr )
{
result.push_back( e_ptr );
try
{
std::rethrow_exception( e_ptr );
}
catch ( std::nested_exception & nested )
{
e_ptr = nested.nested_ptr();
}
catch ( ... )
{
break;
}
}
return result;
}
namespace detail
{
inline std::function<void()> & getGlobalExceptionHandlerRef()
{
static std::function<void()> globalExceptionHandler;
return globalExceptionHandler;
}
}
/// This function installs a global exception handler.
///
/// When calling the function @c handleException() the passed exception handler
/// will be called.
/// The passed exception handler must be thread-safe.
/// The @c handler object is called from within catch blocks only.
///
/// @note This function is not thread-safe, since it accesses static state
/// without mutex protection.
/// Also, this function should not be called while @c handleException() is
/// being called in another thread.
/// This should not be a problem, since this function should be called only
/// once at the start-up of an application before @c handleException() is
/// called.
///
/// In a gui application, the first thing that might be done in the @c main()
/// function is to call
/// @code
/// cu::setGlobalExceptionHandler(
/// qu::getMessageBoxExceptionHandler() );
/// @endcode
/// In other applications exception messages might be written to a file or to
/// another stream, e. g. with
/// @code
/// cu::setGlobalExceptionHandler(
/// cu::getStreamDumpingExceptionHandler( std::cerr ) );
/// @endcode
inline void setGlobalExceptionHandler( std::function<void()> handler )
{
detail::getGlobalExceptionHandlerRef().swap(handler);
}
/// To be used in a catch block to report errors to the user.
///
/// If this function is used outside a catch block, then the behavior is
/// undefined.
///
/// @note Before calling this function, a handling function must be installed
/// with @c setGlobalExceptionHandler().
/// The function @c setGlobalExceptionHandler() should never be called at the
/// same time as this function in an other thread, or undefined behaviour will
/// occur.
///
/// Otherwise, this function is thread-safe.
inline void handleException()
{
assert( std::current_exception() );
assert( detail::getGlobalExceptionHandlerRef() );
detail::getGlobalExceptionHandlerRef()();
}
/// Use this macro to wrap a block of code from which all exceptions shall be
/// handled immediately.
///
/// Example:
/// @code
/// CU_HANDLE_ALL_EXCEPTIONS_FROM
/// {
/// possiblyThrowingOperation();
/// }; // <-- the semicolon is necessary!!
/// @endcode
/// This is equivalent to
/// @code
/// try
/// {
/// possiblyThrowingOperation();
/// }
/// catch(...)
/// {
/// handleException();
/// }
/// @endcode
/// If you try to return from the block (i.e., the keyword 'return' is used),
/// then the surrounding function will not return, but the
/// @c CU_HANDLE_ALL_EXCEPTIONS_FROM{...} expression will evaluate to the
/// return value. Therefore, the keyword 'return' needs to be prepended to
/// @c CU_HANDLE_ALL_EXCEPTIONS_FROM for this to work properly. If the return
/// type is not @c void and an exception is thrown, then a default constructed
/// object will be returned. For this reason, the return type must be void or
/// nothrow default constructible.
#define CU_HANDLE_ALL_EXCEPTIONS_FROM \
::cu::detail::HandleAllExceptionsFromImpl() += [&]()
namespace detail
{
template <typename T>
inline T getDefaultConstructed()
{
return {};
}
template <>
inline void getDefaultConstructed<void>()
{
}
struct HandleAllExceptionsFromImpl
{
template <typename F>
auto operator+=( F && f ) const noexcept
{
using result_type = typename std::result_of<F()>::type;
static_assert( std::is_same<result_type,void>::value ||
std::is_nothrow_default_constructible<result_type>::value,
"The return type of the block must be void or nothrow "
"default constructable. See the documentation of "
"CU_HANDLE_ALL_EXCEPTIONS_FROM." );
try
{
return f();
}
catch(...)
{
handleException();
return getDefaultConstructed<decltype(f())>();
}
}
};
} // namespace detail
/// Wraps a meta functor and handles all exceptions escaping the inside functor.
///
/// A meta functor is a class whose @c operator() takes a functor and executes
/// it, possibly passing a number of arguments. Examples are the @c cu::Monitor
/// class and the @c cu::TaskQueueThread classes. Taking such a class and
/// wrapping it with @c ExceptionHandlingInnerWrapper will manipulate the
/// functors before they are passed to the @c operator() in such a way that
/// all exceptions are caught and handled by @c handleException().
template <typename T>
class ExceptionHandlingInnerWrapper
{
private:
T wrapped;
template <typename F>
class HandlingFunc
{
private:
F f;
public:
HandlingFunc( F && f_ )
: f( std::forward<F>(f_) )
{
}
template <typename ...Args>
auto operator()( Args &&... args )
{
return CU_HANDLE_ALL_EXCEPTIONS_FROM
{
return std::forward<F>(f)( std::forward<Args>(args)...);
};
}
};
public:
template <typename ...Args>
ExceptionHandlingInnerWrapper( Args &&... args )
: wrapped( std::forward<Args>(args)... )
{
}
template <typename F>
auto operator()( F && f ) noexcept
{
return wrapped( HandlingFunc<F>(std::forward<F>(f)) );
}
};
/// Returns the what() message of an @c std::exception.
///
/// If the underlying exception does not derive from @c std::exception,
/// then a generic string is returned.
inline std::string what( const std::exception_ptr & e_ptr )
{
try
{
assert( e_ptr );
std::rethrow_exception( e_ptr );
}
catch ( const std::exception & e )
{
return e.what();
}
catch( ... )
{
return "No problem details available.";
}
}
/// Returns a string containing a numbered list with separated lines.
///
/// The returned string can be something like
/// @code
/// "1. Could not save the file.\n"
/// "2. The file 'my_file.png' could not be opened for writing.\n"
/// @endcode
inline std::string dumpExceptionChain(
const std::vector<std::exception_ptr> & e_ptrs =
getNestedExceptionPtrs( std::current_exception() ) )
{
auto i = 0;
std::string s;
for ( const auto & e_ptr : e_ptrs )
s = s + std::to_string(++i) + ". " + what( e_ptr ) + "\n";
return s;
}
/// Returns a generic exception handler that dumps error messages to a stream.
///
/// @param stream Any stream of a type that can stream @c std::string objects
/// with the @c operator<<(), e. g. std::cerr or a file stream.
template <typename Stream>
auto getStreamDumpingExceptionHandler( Stream & stream )
{
return [&] { stream << "A problem occurred:\n" << dumpExceptionChain(); };
}
} // namespace cu