C++11 (or Boost) system_error strategy
I'm working on a system which is designed to use the classes called error_code
, error_condition
, and error_category
-- a scheme newly std: in C++11, altho at the moment I'm actually using the Boost implementation. I've read Chris Kholkoff's series of articles, three times now, and I think I understand how to create these classes generally.
My issue is that this system needs to handle plugins which exist in individual DLLs, and the plugins may issue errors. My original design was planning one system-specific error category that would encompass all the various error codes and a shortlist of specific error conditions that don't really map to the errno
values. The problem here is that for the DLL to be able to use one of these error codes, it needs access to the sole instance of the error_category
in the app. I'm handling this now by exporting a SetErrorCategory()
function from each DLL, which works but is kinda icky.
The alternate solution I see is that each DLL has its own error category and codes, and if needed, its own conditions; I suspect this is more like what was envisioned for this library feature. But, I think this requires a comparison function in the main app's error scheme that knows about the plugins' error schemes and can check which of the app's conditions match the plugin's error. This seems even more prone to a bunch of problems, altho I haven't tried to implement it yet. I'm guessing I'd have to export the entire error scheme from the DLL, on top of all the actual logic.
Another way to do this, of course, is to just use numeric error codes from the DLL and stuff them into error objects on the app side. It has the advantage of simplicity for the plugin, but could lead to gotchas in the app (e.g., a function juggling objects from a couple different plugins needs to pay attention to the source of each error).
So my specific question is: of those three options, which would you use, and why? Which is obviously unworkable? And of course, is there a better way that hasn't occurred to me?
Solution 1:
The solution I arrived at when working on this problem was to use predefined codes for the family of problem and user subcode selection along with inheritance for the specific type of error. With boost this lets me inherit the specific type by:
struct IOException : virtual std::exception, virtual boost::exception {};
struct EOFException : IOException {};
...
and leave the error code matching the predefined general errors like IOException. Thus I can have a general code range for each family of error:
namespace exception { namespace code {
UNKNOWN_EXCEPTION = 0;
IO_EXCEPTION = 100;
CONCURRENCY_EXCEPTION = 200;
...
}}
Then if someone wants a new error type they can inherit from a generic exception type that's already defined and the code that goes along with that error and specialize the exception by inheritance type and minor value (0-99). This also allows for try catch blocks to catch more specific error types while lettings more general versions of the exception pass to other control blocks. The user is then free to use the parent exception code or specify their own code which is within the family (parent = 100 -> child = 115). If the user just wants an IOError, without creating a new family of errors, they can just use the default family exception with no hassle. I found this gave the user flexibility without requiring OCD tracking of exception codes when they don't want it.
However this is by no means the end-all solution, as personal preference led my design choices here. I find the having too many error codes becomes confusing and that exception inheritance already encodes this information. Actually, in the system I described it's easy to strip out error codes entirely and just rely on exception inheritance, but many people prefer having a code assigned to each exception name.
Solution 2:
I figured out another solution: Create one DLL that contains only my error_category
implementation, and link to it from the app and from each plugin DLL. This gives them all access to the global category object, without having to explicitly pass that object from the app to the DLL.