Clean-up code in the C++ exception's destructor - c++

Can we use the destructor of an exception as a place to put some clean-up code?
In this manner we may allow the client to control the finalization step as opposed to RAII.
Is this a good or a bad design?
Is this a correct solution in the context of OOP and C++?
I'm currently working on an asynchronous procedure which itself starts asynchronously multiple tasks.
The pattern looks as follows:
struct IAsyncResult
{
...
virtual void EndCall() const;
}
typedef std::shared_ptr<IAsyncResult> IAsyncResultPtr;
struct IAsyncTask
{
virtual IAsyncResultPtr BeginTask() = 0;
virtual void EndTask(IAsyncResultPtr async) const = 0;
}
class CompositeTask : public IAsyncTask
{
…
}
Unfortunately I’m unable to guarantee that each subtask’s BeginTask method will not fail. So it is possible that N-1 subtasks would start successfully and the Nth fail.
In general it is vital to be sure that no background tasks are running before the client’s code finishes. But sometimes the client doesn’t care if some tasks fail.
So my current solution involves a custom exception which is thrown from the CompositeTask’s BeginAsync method in case if one task failed to start. This allows a client to control the clean-up stage:
class composite_async_exception : public std::exception
{
std::vector<IAsyncResultPtr> successfully_started_tasks;
mutable bool manage_cleanup;
public:
composite_async_exception(std::vector<IAsyncResultPtr> const& _successfully_started_tasks)
: successfully_started_tasks(_successfully_started_tasks)
, manage_cleanup(true)
{
}
virtual ~composite_async_exception() throw()
{
if(!manage_cleanup)
return;
for( auto task = successfully_started_tasks.begin(); task != successfully_started_tasks.end(); ++task)
{
task->CancelTask();
}
}
void Giveup() const throw()
{
manage_cleanup = false;
}
};
And the client uses the code as shown:
try
{
compositeTask.BeginAsync();
}
catch(composite_async_exception const& ex)
{
//prevent the exception to cancel tasks
ex.Giveup();
// some handling
}
Are there some best practices to handle such a situation?

The exception is eligible to be copied, the destructor would be called multiple times then. In your case that seem not to be a problem.
Exception handling mechanism might stop your tasks by destroying temporary exception object aborting your tasks at throw point, not at handling one.
To verify this one should read standard, which I'm too lazy to do.

Related

How to detect stack unwinding in C++20 coroutines?

The typical advice in C++ is to detect stack unwinding in the destructor using std::uncaught_exceptions(), see the example from https://en.cppreference.com/w/cpp/error/uncaught_exception :
struct Foo {
int count = std::uncaught_exceptions();
~Foo() {
std::cout << (count == std::uncaught_exceptions()
? "~Foo() called normally\n"
: "~Foo() called during stack unwinding\n");
}
};
But this advice looks no longer applicable to C++20 coroutines, which can be suspended and resumed including during stack unwinding. Consider the following example:
#include <coroutine>
#include <iostream>
struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() { return { std::coroutine_handle<promise_type>::from_promise(*this) }; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
void return_void() {}
};
std::coroutine_handle<promise_type> h_;
};
struct Foo {
int count = std::uncaught_exceptions();
Foo() { std::cout << "Foo()\n"; }
~Foo() {
std::cout << (count == std::uncaught_exceptions()
? "~Foo() called normally\n"
: "~Foo() called during stack unwinding\n");
}
};
struct S
{
std::coroutine_handle<ReturnObject::promise_type> h_;
~S() { h_(); }
};
int main()
{
auto coroutine = []() -> ReturnObject { Foo f; co_await std::suspend_always{}; };
auto h = coroutine().h_;
try
{
S s{ .h_ = h };
std::cout << "Exception being thrown\n";
throw 0; // calls s.~S() during stack unwinding
}
catch( int ) {}
std::cout << "Exception caught\n";
h();
h.destroy();
}
It uses the same class Foo inside the coroutine, which is destructed normally (not due to stack unwinding during exception), but still prints:
Exception being thrown
Foo()
Exception caught
~Foo() called during stack unwinding
Demo: https://gcc.godbolt.org/z/Yx1b18zT9
How one can re-design class Foo to properly detect stack unwinding in coroutines as well?
The archetypal reason for wanting to know if a function is being executed due to stack unwinding is for something like rolling back a database transaction. So the situation looks rather like this:
Your function does some database work. It creates a database transaction governed by a RAII object. That object is on the function's stack (either directly or indirectly as a subobject of some other stack object). You do some stuff, and when that RAII object leaves the stack, the database transaction should commit or rollback, depending on whether it left the stack normally or because an exception passed through the function itself respectively.
This is all pretty neat and tidy. There is no explicit cleanup code needed in the function itself.
What does this mean for a coroutine? That becomes exceedingly complicated, because a coroutine can be terminated for reasons outside of its own execution.
For a normal function, it either completes or throws an exception. If such a function fails, it happens internally to the function. Coroutines don't work like that. Between suspend points, the code that schedules the resumption of the coroutine might itself fail.
Consider asynchronous file loading. You pass a continuation function to the file reader, and the continuation will be given the file data as it gets read to process it. Partially through this process, a file read error happens. But that happens in the external code that's accessing the file, not the continuation function that is consuming it.
So the external code needs to tell the consuming function that an error happened and it should abort its process. This cannot happen via an exception (at least not by default); the interface between these two pieces of code must have a mechanism to transmit that the process failed. There are ways to have this interface actually throw an exception within the continuation function itself (ie: the continuation gets some object that it calls to access the currently read data, and it throws if a read error happened), but that is still a cooperative mechanism.
It doesn't happen by itself.
So even if you could solve this problem in a coroutine, you would still need to account for cases when a coroutine needs to terminate for reasons outside of an exception thrown from within. Since you're going to need explicit code to do cleanup/rollbacks/etc anyway, there's little point in relying on purely RAII mechanisms to do this.
To more directly answer the question, if you still want to do this, you need to treat the code between suspend points as if they were their own functions. Each suspend point is effectively a separate function call, with its own exception count and so forth.
So either a RAII object lives entirely between suspend points, or you need to update the exception count every time a suspend point starts.

std::function in combination with thread c++11 fails debug assertion in vector

I want to build a helper class that can accept an std::function created via std::bind) so that i can call this class repeaded from another thread:
short example:
void loopme() {
std::cout << "yay";
}
main () {
LoopThread loop = { std::bind(&loopme) };
loop.start();
//wait 1 second
loop.stop();
//be happy about output
}
However, when calling stop() my current implementation will raise the following error: debug assertion Failed , see Image: i.stack.imgur.com/aR9hP.png.
Does anyone know why the error is thrown ?
I don't even use vectors in this example.
When i dont call loopme from within the thread but directly output to std::cout, no error is thrown.
Here the full implementation of my class:
class LoopThread {
public:
LoopThread(std::function<void(LoopThread*, uint32_t)> function) : function_{ function }, thread_{ nullptr }, is_running_{ false }, counter_{ 0 } {};
~LoopThread();
void start();
void stop();
bool isRunning() { return is_running_; };
private:
std::function<void(LoopThread*, uint32_t)> function_;
std::thread* thread_;
bool is_running_;
uint32_t counter_;
void executeLoop();
};
LoopThread::~LoopThread() {
if (isRunning()) {
stop();
}
}
void LoopThread::start() {
if (is_running_) {
throw std::runtime_error("Thread is already running");
}
if (thread_ != nullptr) {
throw std::runtime_error("Thread is not stopped yet");
}
is_running_ = true;
thread_ = new std::thread{ &LoopThread::executeLoop, this };
}
void LoopThread::stop() {
if (!is_running_) {
throw std::runtime_error("Thread is already stopped");
}
is_running_ = false;
thread_->detach();
}
void LoopThread::executeLoop() {
while (is_running_) {
function_(this, counter_);
++counter_;
}
if (!is_running_) {
std::cout << "end";
}
//delete thread_;
//thread_ = nullptr;
}
I used the following Googletest code for testing (however a simple main method containing the code should work):
void testfunction(pft::LoopThread*, uint32_t i) {
std::cout << i << ' ';
}
TEST(pfFiles, TestLoop)
{
pft::LoopThread loop{ std::bind(&testfunction, std::placeholders::_1, std::placeholders::_2) };
loop.start();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
loop.stop();
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
std::cout << "Why does this fail";
}
Your use of is_running_ is undefined behavior, because you write in one thread and read in another without a synchronization barrier.
Partly due to this, your stop() doesn't stop anything. Even without this UB (ie, you "fix" it by using an atomic), it just tries to say "oy, stop at some point", by the end it does not even attempt to guarantee the stop happened.
Your code calls new needlessly. There is no reason to use a std::thread* here.
Your code violates the rule of 5. You wrote a destructor, then neglected copy/move operations. It is ridiculously fragile.
As stop() does nothing of consequence to stop a thread, your thread with a pointer to this outlives your LoopThread object. LoopThread goes out of scope, destroying what the pointer your std::thread stores. The still running executeLoop invokes a std::function that has been destroyed, then increments a counter to invalid memory (possibly on the stack where another variable has been created).
Roughly, there is 1 fundamental error in using std threading in every 3-5 lines of your code (not counting interface declarations).
Beyond the technical errors, the design is wrong as well; using detach is almost always a horrible idea; unless you have a promise you make ready at thread exit and then wait on the completion of that promise somewhere, doing that and getting anything like a clean and dependable shutdown of your program is next to impossible.
As a guess, the vector error is because you are stomping all over stack memory and following nearly invalid pointers to find functions to execute. The test system either puts an array index in the spot you are trashing and then the debug vector catches that it is out of bounds, or a function pointer that half-makes sense for your std function execution to run, or somesuch.
Only communicate through synchronized data between threads. That means atomic data, or mutex guarded, unless you are getting ridiculously fancy. You don't understand threading enough to get fancy. You don't understand threading enough to copy someone who got fancy and properly use it. Don't get fancy.
Don't use new. Almost never, ever use new. Use make_shared or make_unique if you absolutely have to. But use those rarely.
Don't detach a thread. Period. Yes this means you might have to wait for it to finish a loop or somesuch. Deal with it, or write a thread manager that does the waiting at shutdown or somesuch.
Be extremely clear about what data is owned by what thread. Be extremely clear about when a thread is finished with data. Avoid using data shared between threads; communicate by passing values (or pointers to immutable shared data), and get information from std::futures back.
There are a number of hurdles in learning how to program. If you have gotten this far, you have passed a few. But you probably know people who learned along side of you that fell over at one of the earlier hurdles.
Sequence, that things happen one after another.
Flow control.
Subprocedures and functions.
Looping.
Recursion.
Pointers/references and dynamic vs automatic allocation.
Dynamic lifetime management.
Objects and Dynamic dispatch.
Complexity
Coordinate spaces
Message
Threading and Concurrency
Non-uniform address spaces, Serialization and Networking
Functional programming, meta functions, currying, partial application, Monads
This list is not complete.
The point is, each of these hurdles can cause you to crash and fail as a programmer, and getting each of these hurdles right is hard.
Threading is hard. Do it the easy way. Dynamic lifetime management is hard. Do it the easy way. In both cases, extremely smart people have mastered the "manual" way to do it, and the result is programs that exhibit random unpredictable/undefined behavior and crash a lot. Muddling through manual resource allocation and deallocation and multithreaded code can be made to work, but the result is usually someone whose small programs work accidentally (they work insofar as you fixed the bugs you noticed). And when you master it, initial mastery comes in the form of holding an entire program's "state" in uour head and understanding how it works; this fails to scale to large many-developer code bases, so younusually graduate to having large programs that work accidentally.
Both make_unique style and only-immutable-shared-data based threading are composible strategies. This means if small pieces are correct, and you put them together, the resulting program is correct (with regards to resource lifetime and concurrency). That permits local mastery of small-scale threading or resource management to apply to larfe-scale programs in the domain that these strategies work.
After following the guide from #Yakk i decided to restructure my programm:
bool is_running_ will change to td::atomic<bool> is_running_
stop() will not only trigger the stopping, but will activly wait for the thread to stop via a thread_->join()
all calls of new are replaced with std::make_unique<std::thread>( &LoopThread::executeLoop, this )
I have no experience with copy or move constructors. So i decided to forbid them. This should prevent me from accidently using this. If i sometime in the future will need those i have to take a deepter look on thoose
thread_->detach() was replaced by thread_->join() (see 2.)
This is the end of the list.
class LoopThread {
public:
LoopThread(std::function<void(LoopThread*, uint32_t)> function) : function_{ function }, is_running_{ false }, counter_{ 0 } {};
LoopThread(LoopThread &&) = delete;
LoopThread(const LoopThread &) = delete;
LoopThread& operator=(const LoopThread&) = delete;
LoopThread& operator=(LoopThread&&) = delete;
~LoopThread();
void start();
void stop();
bool isRunning() const { return is_running_; };
private:
std::function<void(LoopThread*, uint32_t)> function_;
std::unique_ptr<std::thread> thread_;
std::atomic<bool> is_running_;
uint32_t counter_;
void executeLoop();
};
LoopThread::~LoopThread() {
if (isRunning()) {
stop();
}
}
void LoopThread::start() {
if (is_running_) {
throw std::runtime_error("Thread is already running");
}
if (thread_ != nullptr) {
throw std::runtime_error("Thread is not stopped yet");
}
is_running_ = true;
thread_ = std::make_unique<std::thread>( &LoopThread::executeLoop, this );
}
void LoopThread::stop() {
if (!is_running_) {
throw std::runtime_error("Thread is already stopped");
}
is_running_ = false;
thread_->join();
thread_ = nullptr;
}
void LoopThread::executeLoop() {
while (is_running_) {
function_(this, counter_);
++counter_;
}
}
TEST(pfThread, TestLoop)
{
pft::LoopThread loop{ std::bind(&testFunction, std::placeholders::_1, std::placeholders::_2) };
loop.start();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
loop.stop();
}

Managing thread life-cycle in derived class

I have a Base class which acts as an interface to multiple strategies for synchronous event processing. I now want the strategies to process the events asynchronously. To minimize code refactor, each strategies will have its own internal thread for asynchronous event processing. My main concern is how to manage the lifecycle of this thread. The Derived strategies classes are constructed and destructed all around the codebase so it would be hard to manage the thread lifecycle (start/stop) outside of the strategies classes.
I ended up with the following code:
#include <iostream>
#include <cassert>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
struct Base
{
virtual ~Base()
{
std::cout << "In ~Base()" << std::endl;
// For testing purpose: spend some time in Base dtor
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
}
virtual void processEvents() = 0;
void startThread()
{
if(_thread)
{
stopThread();
}
_thread.reset(new boost::thread(&Base::processEvents, this));
assert(_thread);
}
void stopThread()
{
if(_thread)
{
std::cout << "Interrupting and joining thread" << std::endl;
_thread->interrupt();
_thread->join();
_thread.reset();
}
}
boost::shared_ptr<boost::thread> _thread;
};
struct Derived : public Base
{
Derived()
{
startThread();
}
virtual ~Derived()
{
std::cout << "In ~Derived()" << std::endl;
// For testing purpose: make sure the virtual method is called while in dtor
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
stopThread();
}
virtual void processEvents()
{
try
{
// Process events in Derived specific way
while(true)
{
// Emulated interruption point for testing purpose
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
std::cout << "Processing events..." << std::endl;
}
}
catch (boost::thread_interrupted& e)
{
std::cout << "Thread interrupted" << std::endl;
}
}
};
int main(int argc, char** argv)
{
Base* b = new Derived;
delete b;
return 0;
}
As you can see, the thread is interrupted and joined in the Derived class destructor. Many comments on Stackoverflow argues that it's a bad idea to join a thread in a destructor. However, I can't find a better idea considering the constraint that the thread lifecycle must be managed through the construction/destruction of the Derived class. Does someone has a better proposition?
It is a good idea to release resources a class creates when the class is destroyed, even if one of the resources is a thread. However, when performing any non-trivial task in a destructor, it is often worth taking the time to examine the implications in full.
Destructors
A general rule is to not throw exceptions in destructors. If a Derived object is on a stack that is unwinding from another exception, and Derived::~Derived() throws an exception, then std::terminate() will be invoked, killing the application. While Derived::~Derived() is not explicitly throwing an exception, it is important to consider that some of the functions it is invoking may throw, such as _thread->join().
If std::terminate() is the desired behavior, then no change is required. However, if std::terminate() is not desired, then catch boost::thread_interrupted and suppress it.
try
{
_thread->join();
}
catch (const boost::thread_interrupted&)
{
/* suppressed */
}
Inheritance
It looks as though inheritance was used to for code reuse and minimizing code refactoring by isolating the asynchronous behavior to be internal to the Base hierarchy. However, some of the boilerplate logic is also in Dervied. As classes derived from Base are already having to be changed, I would suggest considering aggregation or the CRTP to minimize the amount of boilerplate logic and code within these classes.
For example, a helper type can be introduced to encapsulate the threading logic:
class AsyncJob
{
public:
typedef boost::function<void()> fn_type;
// Start running a job asynchronously.
template <typename Fn>
AsyncJob(const Fn& fn)
: thread_(&AsyncJob::run, fn_type(fn))
{}
// Stop the job.
~AsyncJob()
{
thread_.interrupt();
// Join may throw, so catch and suppress.
try { thread_.join(); }
catch (const boost::thread_interrupted&) {}
}
private:
// into the run function so that the loop logic does not
// need to be duplicated.
static void run(fn_type fn)
{
// Continuously call the provided function until an interrupt occurs.
try
{
while (true)
{
fn();
// Force an interruption point into the loop, as the user provided
// function may never call a Boost.Thread interruption point.
boost::this_thread::interruption_point();
}
}
catch (const boost::thread_interrupted&) {}
}
boost::thread thread_;
};
This helper class could be aggregated and initialized in Derived's constructor. It removes the need for much of the boilerplate code, and can be reused elsewhere:
struct Derived : public Base
{
Derived()
: job_(boost::bind(&Base::processEvents, this))
{}
virtual void processEvents()
{
// Process events in Derived specific way
}
private:
AsyncJob job_;
};
Another key point is that the AsyncJob forces a Boost.Thread interruption point into the loop logic. The job shutdown logic is implemented in terms of interruption points. Thus, it is critical that an interruption point be reached during iterations. Otherwise, it could be possible to end up in a deadlock if the user code never reaches an interruption point.
Lifespan
Examine whether it is the thread's lifetime that must be associated with the object's lifetime, or if it is the asynchronous event processing that needs to be associated with the object's lifetime. If it is the latter, then it may be worth considering using thread pools. A thread pool could provide finer grain control over thread resources, such as imposing a maximum limit, as well as minimize the amount of wasted threads, such as threads doing nothing or time spent creating/destroying short-lived threads.
For example, consider the case where a user creates an array of 500 Dervied classes. Are 500 threads needed to handle 500 strategies? Or could 25 threads handle 500 strategies? Keep in mind that on some systems, thread creation/destruction can be expensive, and there may even be maximum thread limit imposed by the OS.
In conclusion, examine the tradeoffs, and determine which behaviors are acceptable. It can be difficult to minimize code refactoring, particularly when changing the threading model that has implications to various areas of the codebase. The perfect solution is very rarely obtainable, so identify the solution that covers the majority of cases. Once the supported behavior has been clearly defined, work on modifying existing code so that it is within the supported behavior.

Cancelling a thread running a long operation

I'm trying to work out a design predicament I have.
ClassWithLongOperation
{
Run()
{
RecrusiveOperation();
}
RecrusiveOperation()
{
/* RECURSION */
}
}
MyThread
{
ClassWithLongOperation Op1(10);
Op1.Run(); // Takes several minutes.
ClassWithLongOperation Op2(20);
Op2.Run();
SomeOtherClassWithLongOperation Op3;
Op3.Run();
// Do some other stuff
}
The GUI starts MyThread, which runs for a good 5-6 minutes. I want to be able to have a big fat Cancel button on my GUI, so the user can cancel the operation.
I could create a global boolean variable bCancelled, and check if its been set in RecursiveOperation, but I want to be a good C++ & OO programmer and avoid global variables. Especially if they would have to spread across multiple files.
So how would I (following good design) safely cancel MyThread? What could I change in my setup to allow this?
I'm also using _beginthreadex to start the thread, but I could use boost if it would allow for an easier solution.
Your flag not need to be global to your entire program, but it needs to be visible to your class code. Create the flag to be a private instance member and a public function to change it to false/true. In your recursive function, test its value to verify if the task should continue. When you want, set its value to false (through the function of course) to stop the recursive calls, i.e., when the user clicks the button you call the function in the desired instance. This way you will not break any OO principle, since you have a private flag and a public member function to safely change it.
Using a global variable is actually not the worst thing in the world. Having a proliferation of unnecessary global variables leads to maintenance nightmares, but it actually sounds like a quick and easy-to-understand solution here. But if you want a clean OO solution, this is certainly possible:
EDIT My original post overlooked the fact that you want to be able to run several operations in sequence, and if any of them is cancelled, none of the remaining operations are performed. This means it's more useful to keep the bool flag inside the canceller, instead of separately in each cancellable operation; and exceptions are the nicest way to handle the actual control flow. I've also tightened up a few things (added volatile for the flag itself, made names clearer, restricted unnecessary access rights).
// A thing that can cancel another thing by setting a bool to true.
class Canceller {
public:
Canceller : cancelledFlag(false) {}
void RegisterCancellee(Cancellee const& c) {
c.RegisterCanceller(cancelledFlag);
}
void Cancel() {
cancelledFlag = true;
}
private:
volatile bool cancelledFlag;
};
class CancelButton : public Canceller {
...
// Call Cancel() from on-click event handler
...
};
class Cancellation : public std::exception {
public:
virtual const char* what() const throw() {
return "User cancelled operation";
}
};
// A thing that can be cancelled by something else.
class Cancellee {
friend class Canceller; // Give them access to RegisterCanceller()
protected:
Cancellee() : pCancelledFlag(0) {}
// Does nothing if unconnected
void CheckForCancellation() {
if (pCancelledFlag && *pCancelledFlag) throw Cancellation();
}
private:
void RegisterCanceller(volatile bool& cancelledFlag) {
pCancelledFlag = &cancelledFlag;
}
volatile bool* pCancelledFlag;
};
class Op1 : public Cancellee { // (And similarly for Op2 and Op3)
...
// Poll CheckForCancellation() inside main working loop
...
};
MyThread
{
CancelButton cancelButton("CANCEL!");
try {
ClassWithLongOperation Op1(10);
cancelButton.RegisterCancellee(Op1);
Op1.Run(); // Takes several minutes.
ClassWithLongOperation Op2(20);
cancelButton.RegisterCancellee(Op2);
Op2.Run();
SomeOtherClassWithLongOperation Op3;
cancelButton.RegisterCancellee(Op3);
Op3.Run();
} catch (Cancellation& c) {
// Maybe write to a log file
}
// Do some other stuff
}
The "double bouncing" registration allows the canceller to give access to a private flag variable.
The most important thing is to not use thread termination functions, except in very specialised cases. Why? They don't run destructors. Nor do they give the target thread any chance to "clean up".
Instead of using a global variable, add a method to ClassWithLongOperation and/or MyThread, something like cancelOperation() that will set an internal boolean variable. The appropriate class methods would then need to check the variable at appropriate moments.
You could implement a Stop() method for your ClassWithLongOperation and have the event handler for BigFatCancelButton to call this Stop() method for the current operation.
... Or add a Stop() method to the Thread class and make the work objects be aware of the threads they're running in. You may as well throw in a Stop() method for the work objects. Depending on what's more important: Stop the thread or the work object.

Dispatching exceptions in C++

How should exceptions be dispatched so that error handling and diagnostics can be handled in a centralized, user-friendly manner?
For example:
A DataHW class handles communication with some data acquisition hardware.
The DataHW class may throw exceptions based on a number of possible errors: intermittent signal, no signal, CRC failure, driver error. Each type of error gets its own exception class.
The DataHW class is called by a number of different pieces of code that do different types of acquisition and analysis.
The proper error handling strategy depends on the type of exception and the operation being attempted. (On intermittent signal, retry X times then tell the user; on a driver error, log an error and restart the driver; etc.) How should this error handling strategy be invoked?
Coding error recovery into each exception class: This would result in exception classes that are rather large and contain high-level UI and system management code. This seems bad.
Providing a separate catch block for each type of exception: Since the DataHW class is called from many different places, each catch block would have to be duplicated at each call site. This seems bad.
Using a single catch block that calls some ExceptionDispatch function with a giant RTTI-based switch statement: RTTI and switch usually indicates a failure to apply OO design, but this seems the least bad alternative.
Avoid duplicating the catch blocks at each call site by catching (...) and calling a shared handler function which rethrows and dispatches:
f()
{
try
{
// something
}
catch (...)
{
handle();
}
}
void handle()
{
try
{
throw;
}
catch (const Foo& e)
{
// handle Foo
}
catch (const Bar& e)
{
// handle Bar
}
// etc
}
An idea I keep running into is that exceptions should be caught by levels which can handle them. For example, a CRC error might be caught by the function that transmits the data, and upon catching this exception, it might try to retransmit, whereas a "no signal" exception might be caught in a higher level and drop or delay the whole operation.
But my guess is that most of these exceptions will be caught around the same function. It is a good idea to catch and handle them seperately (as in soln #2), but you say this causes a lot of duplicate code (leading to soln #3.)
My question is, if there is a lot of code to duplicate, why not make it into a function?
I'm thinking along the lines of...
void SendData(DataHW* data, Destination *dest)
{
try {
data->send(dest);
} catch (CRCError) {
//log error
//retransmit:
data->send(dest);
} catch (UnrecoverableError) {
throw GivingUp;
}
}
I guess it would be like your ExceptionDispatch() function, only instead of switching on the exception type, it would wrap the exception-generating call itself and catch the exceptions.
Of course, this function is overly simplified - you might need a whole wrapper class around DataHW; but my point is, it would be a good idea to have a centralized point around which all DataHW exceptions are handled - if the way different users of the class would handle them are similar.
Perhaps you could write a wrapper class for the DataHW class?
The wrapper would offer the same functionality as the DataHW class, but also contained the needed error handling code. Benefit is that you have the error handling code in a single place (DRY principle), and all errors would be handled uniformly. For example you can translate all low level I/O exceptions to higher level exceptions in the wrapper.
Basically preventing low level exceptions being showed to user.
As Butler Lampson said: All problems in computer science can be solved by another level of indirection
There are three ways i see to solve this.
Writing wrapper functions
Write a wrapper function for each function that can throw exceptions which would handle exceptions. That wrapper is then called by all the callers, instead of the original throwing function.
Using function objects
Another solution is to take a more generic approach and write one function that takes a function object and handles all exceptions. Here is some example:
class DataHW {
public:
template<typename Function>
bool executeAndHandle(Function f) {
for(int tries = 0; ; tries++) {
try {
f(this);
return true;
}
catch(CrcError & e) {
// handle crc error
}
catch(IntermittentSignalError & e) {
// handle intermittent signal
if(tries < 3) {
continue;
} else {
logError("Signal interruption after 3 tries.");
}
}
catch(DriverError & e) {
// restart
}
return false;
}
}
void sendData(char const *data, std::size_t len);
void readData(char *data, std::size_t len);
};
Now if you want to do something, you can just do it:
void doit() {
char buf[] = "hello world";
hw.executeAndHandle(boost::bind(&DataHW::sendData, _1, buf, sizeof buf));
}
Since you provide function objects, you can manage state too. Let's say sendData updates len so that it knows how much bytes were read. Then you can write function objects that read and write and maintain a count for how many characters are read so far.
The downside of this second approach is that you can't access result values of the throwing functions, since they are called from the function object wrappers. There is no easy way to get the result type of a function object binder. One workaround is to write a result function object that is called by executeAndHandle after the execution of the function object succeeded. But if we put too much work into this second approach just to make all the housekeeping work, it's not worth the results anymore.
Combining the two
There is a third option too. We can combine the two solutions (wrapper and function objects).
class DataHW {
public:
template<typename R, typename Function>
R executeAndHandle(Function f) {
for(int tries = 0; ; tries++) {
try {
return f(this);
}
catch(CrcError & e) {
// handle crc error
}
catch(IntermittentSignalError & e) {
// handle intermittent signal
if(tries < 3) {
continue;
} else {
logError("Signal interruption after 3 tries.");
}
}
catch(DriverError & e) {
// restart
}
// return a sensible default. for bool, that's false. for other integer
// types, it's zero.
return R();
}
}
T sendData(char const *data, std::size_t len) {
return executeAndHandle<T>(
boost::bind(&DataHW::doSendData, _1, data, len));
}
// say it returns something for this example
T doSendData(char const *data, std::size_t len);
T doReadData(char *data, std::size_t len);
};
The trick is the return f(); pattern. We can return even when f returns void. This eventually would be my favorite, since it allows both to keep handle code central at one place, but also allows special handling in the wrapper functions. You can decide whether it's better to split this up and make an own class that has that error handler function and the wrappers. Probably that would be a cleaner solution (i think of Separation of Concerns here. One is the basic DataHW functionality and one is the error handling).