Throw error on max instantiations of class - c++

I want my program to throw an error when a defined maximum number of objects of a certain class (MAX) is reached. I must limit the number of instantiations because I'm working with a framework that provides a limited amount of resources.
Currently I have the following (simplified):
class Resource {
private:
static int count;
public:
Resource();
};
int Resource::count = 0;
Resource::Resource() {
if (++count > MAX) {
throw std::domain_error("Cannot create more resources.");
}
}
Is the use of std::domain_error recommended or should I use another type? Or should I not throw an error at all and follow a different approach?

I will answer the general question, without considering any of the details of the application.
Is the use of std::domain_error recommended or should I use another
type? Or should I not throw an error at all and follow a different
approach?
Regarding which exception to throw: std::domain_error specifically has to do with the argument of a function being outside the allowed range, so that is not a good fit. As #NathanOliver suggests, std::runtime_error would be a good option. Or a custom exception derived from that.
Regarding throwing an exception or using a different approach: An important question is when the error is expected to happen and who can handle it.
Exceptions are typically used for errors that can happen during normal operation, and preferably be handled at run-time.
If the amount of resources allocated is determined during development, and excessive allocation of resources indicates a bug, then an assertion is an option:
Resource::Resource() {
assert(++count <= MAX);
}
That will make a debug-build of the program crash (fail-fast) if the limit is exceeded, which is often quite useful during development, testing, and debugging but should obviously not be used if the limit can be exceeded in the finished product. (As assertions are typically removed in release builds it would go unnoticed and cause whatever problems or undefined behaviour exceeding the limit would cause).

Related

Distinguishing between multiple exceptions of the same type

I can't quite wrap my head around how a user will be able to distinguish between the exceptions my functions can throw. One of my functions can throw two instances of std::invalid_argument.
For example, in a constructor:
#include <stdexcept> // std::invalid_argument
#include <string>
class Foo
{
public:
void Foo(int hour, int minute)
:h(hour), m(minute)
{
if(hour < 0 || hour > 23)
throw std::invalid_argument(std::string("..."));
if(minute < 0 || minute > 59)
throw std::invalid_argument(std::string("..."));
}
}
Note: It's an example, please do not answer with bounded integers.
Say the user calls with foo(23, 62);, how would the user's exception handler distinguish between the two possible instances of std::invalid_argument?
Or am I doing this wrong, and I should inherit from std::invalid_argument to distinguish between them? That is,
class InvalidHour: public std::invalid_argument
{
public:
InvalidHour(const std::string& what_arg)
:std::invalid_argument(msg) {};
}
class InvalidMinute: public std::invalid_argument
{
public:
InvalidMinute(const std::string& what_arg)
:std::invalid_argument(msg) {};
}
Then, throw InvalidHour and InvalidMinute?
Edit: Creating a class for every possible exception seems a little too much to me, especially in a large program. Does every program that effectively uses exceptions this way come with extensive documentation on what to catch?
As mentioned in an answer, I have considered assert to. Looking around stackoverflow, I have found a majority of people saying you should throw an exception (as my particular case is for a constructor).
After looking around a lot of online information on when to use exceptions, the general consensus is to use an assert for logic errors and exceptions for runtime errors. Although, calling foo(int, int) with invalid arguments could be a runtime error. This is what I want to address.
The standard exception hierarchy is unsuitable for logic errors. Use an assert and be done with it. If you absolutely do want to transform hard bugs into harder to detect run time errors, then note that there are only two reasonable things a handler can do: achieve the contractual goal in some possibly different way (possibly just retrying the operation), or in turn throw an exception (usually just rethrowing), and that the exact cause of the original exception seldom plays any rĂ´le in this. Finally, if you do want to support code that really tries various combinations of arguments until it finds one that doesn't throw, no matter how silly that appears now that it's written out in words, well, you have std::system_error for passing an integer error code up, but you can define derived exception classes.
All that said, go for the assert.
That's what it's for.
You could also create further error classes that derive from invalid_argument, and that would make them distinguishable, but this is not a solution that scales. If what you actually want is to show the suer a message that he can understand, then the string parameter to the invalid_argument would serve that purpose.
The standard exceptions do not allow storing the additional information you want, and parsing exception messages is a bad idea. One solution is to subclass, as you mention. There are others - with the advent of std::exception_ptr. it is possible to use "inner" (or "nested") exceptions as in Java or .NET, though this feature is more applicable to exception translation. Some prefer Boost.Exception, as another solution for exceptions extensible at runtime.
Don't fall into the "just assert trap" like Cheers and hth. Simple example:
void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen)
{
assert( fromLen <= bufLen );
std::copy(from, from + fromLen, buf);
}
There's nothing wrong with the assert per se, but if the code is compiled for release (with NDEBUG set), then safe_copy will not be safe at all, and the result may be a buffer overrun, potentially allowing a malicious party to take over the process. Throwing an exception to indicate a logic error has its own problems, as mentioned, but at least it will prevent the immediate undefined behavior in the release build. I'd therefore suggest, in security-critical functions, to use assertions in the debug, and exceptions in the release build:
void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen)
{
assert( fromLen <= bufLen );
if ( fromLen > bufLen )
throw std::invalid_argument("safe_copy: fromLen greater than bufLen");
std::copy(from, from + fromLen, buf);
}
Of course, if you use this pattern a lot, you may wish to define a macro of your own to simplify the task. This is beyond the scope of the current topic, however.
Two other reasons to throw exceptions rather than have assertions is when you are implementing a library or some form of exportable code and cannot tell apriori how a user will want to handle some form of error or when you need checking when the user builds your code in RELEASE mode (which users often do). Note that building in RELEASE mode "takes away" any assertions.
For example, take a look at this code:
struct Node
{
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};
// some code
Node* n = new Node(5);
assert(n && "Nodes can't be null");
// use n
When this code is build in RELEASE mode, that assertion "doesn't exist" and the caller might get n being nullptr at runtime.
If the code threw an exception instead of the assertion, the caller can still "react" to a nullptr anomaly in both debug and release builds. The downside is that the exception approach requires a lot more boiler plate code.

Throw runtime warnings in C++

I started using exceptions some weeks ago and now I wonder if there is a way to just throw a warning. This warning shouldn't force the application to exit if it isn't caught. I will give you an example in what situation I would like to use that.
There is a system that appends properties to unique ids. When I somehow try to add a property to a not yet existing id, the system should create that id internally for me, add the property to it afterwards and return the result. Of course this can't be done quietly. But since the application could stay running, I do not want to throw an exception.
How can I notify that something wasn't quite correct, but the system runs on?
Who do you want to notify? The end-user? In which case, just write a suitable message to cerr. Or better, write a wrapper function (e.g. LOG_WARNING()) to do it in a controlled manner. Or better still, use a logging framework.
But since the application could stay running, I do not want to throw an exception.
Note that an exception doesn't have to result in the application terminating. You can catch an exception higher up the stack, and handle the situation appropriately.
No, that's not possible. You can only throw and catch exceptions. If you want to be cheeky you could do
class warning : public std::exception
{
public:
warning(const std::string& msg) {}
const char* what() { return msg.c_str(); } //message of warning
private:
std::string msg;
};
Then you can:
throw warning("this is a warning");
This could be an artificially made up warning system if you want.
While there's no throwing a warning. I believe the functionality you're looking for is available from errno
You can set it to any of the standard errors or make up your own error codes. (Please document them well though.)
This could be useful if your library is meant to be used by other developers. An example of when this might be useful is with a JSON parser. JSON supports arbitrarily large numbers with arbitrary accuracy. So if internally your parser uses doubles to represent numbers if it encountered a number that it couldn't represent then it could round the number to the nearest representable number the set errno=EDOM; (argument out of range) that way, it leaves the decision up to the developers as to whether the rounding will matter. If you want to be super nice you could even add in a way to retrieve the locations of the rounds possibly even with the original text.
All of that said, this should only be used in situations where:
the warning really can be bypassed completely in some scenarios
the root source of the warning is input to the library you're writing
the in some situations the consumer of the library might care about the warning but most of the time wouldn't.
there's not a more suitable way to return the information (like a status passed by reference with an overload that doesn't require the status)
Just print a message to stderr or to your logs.

How should I handle invalid values passed to a setter method?

If I have a simple class such as...
class Rectangle
{
private:
double length;
double width;
public:
void setLength(double l) { length = l; }
void setWidth(double w) { width = l; }
void getLength() { return length; }
void getWidth() { return width; }
void getArea() { return length * width; }
};
...and the person using it calls the mutator setWidth() with an invalid argument, say -1.0, what is the correct way to handle this? When I say correct, should I for example, change the return type from void to bool or possibly an int and return a value to indicate whether or not the operation occurred successfully, or am I to allow the value to be set since theoretically it will not break anything, but any values returned as a result, say from getArea() will be garbage and the user will have to debug what he/she did wrong?
Sorry if this is a silly question, but two text books later, and I'm still not quite sure about how classes I write should be implemented for others to use.
You have a couple choices. In the real world, it will depend on who is using your class (other internal code or public API) as well as conventions agreed upon by your company.
You can:
Throw Exception
Pros:
Simple to implement
Keeps error handling code clean
Allows passing of error messages and other variables
Standard for most exceptional situations
Harder to ignore than a boolean return
Can be thrown from constructor
Cons:
Can be overused in areas where normal conditional statements should be used (this situation doesn't count)
You are introducing more "invisible" breaking points into an application. Many times users of a class don't know what exactly is going to be thrown because it either isn't documented or he/she didn't read the documentation.
Assert
Pros:
Rule of thumb: if you are going to crash, crash early
Good for situations where you as the developer call the code and have control over the input
Strong debugger support (typically)
Cons:
Gets excluded from non-debug builds so not good for times when user can still input bad data
Boolean Returns
Pros:
Quick/Easy to implement
Smaller footprint than exception handling (although this is negligible these days)
Cons:
Can't pass back any extra information to the caller (why did it break?)
Breaks down when you are trying to be consistent through your entire project and a simple boolean won't cut it
Easier to ignore compared to exceptions
Evaluate Data Type
Don't be afraid to go back and check if the data type of your property makes sense for what you are doing. In this case, it does. However, I can think of times where developers have used standard integers when they really wanted unsigned integers or strings when they really needed a single character.
You should throw an exception:
#include <stdexcept>
// ...
void setLength(double len)
{
if (len < 0)
throw new std::invalid_argument("len");
length = len;
}
Depends on the usage of the class.
If you want a user friendly message to be passed on to caller, you can put an if condition and throw an exception (e.g. std::invalid_argument) with a good description. Update the documentation and signature appropriately of course.
If you expect this should never happen in real life, but can happen in, say testing phase of the development, then I recommend using assert. As in production/release build the asserts are "removed" from code by compiler. So while testing (debug build, e.g. -DNDEBUG and/or -g option for gcc) if someone makes a mistake, you'll see a clear assertion failure, but in real/production environment no error would be reported and the extra condition won't harm the performance.
Usually when compiler is invoked with -DNDEBUG option, it's
Some references for assert and throw syntax.
Either return false; or throw something like invalid_value.
How an error should be handled is completely requirement/project dependent. If your project require to continue with wrong user input (which could be very dangerous in real life software) that's what you need to do. Similarly, if the project needs protection against wrong user input, that must be addressed.
When you design a class to be used by other user, you must make sure it can't be used any wrong way. You must fool proof your code. If any bug emerges from the software where your class will be used, you should write the class in such a way so that the bug doesn't come out from your code.

C++ exception handling and error reporting idioms

In C++, RAII is often advocated as a superior approach to exception handling: if an exception is thrown, the stack is unwound, all the destructors are called and resources are cleaned up.
However, this presents a problem with error reporting. Say a very generic function fails, the stack is unwound to the top level and all I see in the logs would be:
Couldn't read from socket: connection reset by peer.
...or any equally generic message. This doesn't say much about the context from which the exception is thrown. Especially if I'm running something like an event queue processing loop.
Of course I could wrap every call to socket reads with a try/catch block, catch the exception, construct a new one with more detailed context information and re-throw it, but it defeats the purpose of having RAII, and is slowly but surely becoming worse than handling return error codes.
What's a better way for detailed for error reporting in standard C++? I'm also open to suggestions involving Boost.
As James McNellis suggested here, there is a really neat trick involving a guard object and the std::uncaught_exception facility.
The idea is to write code like this:
void function(int a, int b)
{
STACK_TRACE("function") << "a: " << a << ", b: " << b;
// do anything
}
And have the message logged only in case an exception is actually thrown.
The class is very simple:
class StackTrace: boost::noncopyable // doesn't make sense to copy it
{
public:
StackTrace(): mStream() {}
~StackTrace()
{
if (std::uncaught_exception())
{
std::cout << mStream.str() << '\n';
}
}
std::ostream& set(char const* function, char const* file, unsigned int line)
{
return mStream << file << "#" << line << " - " << function << " - ";
}
private:
std::ostringstream mStream;
};
#define STACK_TRACE(func) \
StackTrace ReallyUnwieldyName; \
ReallyUnwieldyName.set(func, __FILE__, __LINE__)
One can use __PRETTY_FUNC__ or equivalent to avoid naming the function, but I have found in practice that it was too mangled / verbose for my own taste.
Note that you need a named object if you wish it to live till the end of the scope, which is the purpose here. We could think of tricky ways to generate a unique identifier, but I have never needed it, even when protecting narrower scope within a function, the rules of name hiding play in our favor.
If you combine this with an ExceptionManager (something where exceptions thrown register themselves), then you can get a reference to the latest exception and in case of logging you can rather decide to setup your stack within the exception itself. So that it's printed by what and ignored if the exception is discarded.
This is a matter of taste.
Note that in the presence of ExceptionManager you must remain aware that not all exceptions can be retrieved with it --> only the ones you have crafted yourself. As such you still need a measure of protection against std::out_of_range and 3rd party exceptions.
Say a very generic function fails, the stack is unwound to the top level and all I see in the logs would be [...]
What's a better way for detailed for error reporting in standard C++?
Error handling isn't local to a class or library - it is a application level concern.
Best I can answer you question is that the error reporting should be always implemented by looking first and foremost at the error handling. (And the error handling also including the handling of the error by the user.) Error handling is the decision making about what has to be done about the error.
That is one of the reasons why error reporting is an application level concern and strongly depends on the application workflow. In one application the "connection reset by peer" is a fatal error - in another it is a norm of life, error should be silently handled, connection should be reestablished and pending operation retried.
Thus the approach you mention - catch the exception, construct a new one with more detailed context information and re-throw it - is a valid one too: it is up to the top level application logic (or even user configuration) to decide whether the error is really an error or some special (re)action has to taken upon the condition.
What you have encountered is one of the weaknesses of so called out-of-line error handling (aka exceptions). And I'm not aware of any way to do it better. Exceptions create a secondary code path in the application and if error reporting is vital the design of the secondary code path has to treated just like the main code path.
Obvious alternative to the out-of-line error handling is the in-line error handling - good ol' return codes and log messages on the error conditions. That allows for a trick where application saves all low-severity log messages into internal (circular) buffer (of fixed or configurable size) and dumps them into the log only if high-severity error happens. This way more context information is available and different layers of application do not have to be actively aware of each other. That is also standard (and sometimes literally "standard" - mandated by law) way of error reporting in application fields like safety and mission critical software, were one is not allowed to miss errors.
I've never actually done this, but you could roll your own "stacktrace":
struct ErrorMessage {
const char *s;
ErrorMessage(const char *s) : msg(s) {}
~ErrorMessage() { if (s) std::cout << s << "\n"; }
void done() { s = 0; }
};
void someOperation() {
ErrorMessage msg("Doing the first bit");
// do various stuff that could throw
msg = "Doing the second bit";
// do more stuff that could throw
msg.done();
}
You can have several levels of this (although not necessarily use it at every level):
void handleFoo() {
ErrorMessage msg("Handling foo event");
someOperation();
msg.done();
}
And add more constructors and members:
void handleBar(const BarEvent &b) {
ErrorMessage msg(std::stringstream("Handling bar event ") << b.id);
someOperation();
msg.done();
}
And you needn't write the messages to std::cout. It could be to some logging object, and the object could queue them, and not actually output them to the log unless the catch site tells it to. That way, if you catch an exception that doesn't warrant logging, nothing is written.
It's not pretty, but it's prettier than try/catch/throw or checking return values. And if you forget to call done on success (for example if your function has multiple returns and you miss one), then you will at least see your mistake in the logs, unlike a resource leak.
[Edit: oh, and with a suitable macro you can store __FILE__ and __LINE__ in the ErrorMessage.]
You could add a call stack to your exception. I'm not sure how good this works for release builds but works like a charm with debug. You could do this in the constructor of your exception (to encapsulate it). See here for a starting point. This is possible as well on Linux - eventhough I dont remember how exactly.
I use RAII and exceptions and just have various unit-test-like assert statements throughout the code - if they fail, the the stack unwinds to where I can catch and handle them.
#define APP_ASSERT_MSG(Class,Assertion,szDescription) \
if ( !(Assertion) ) \
{ \
throw Class(__LINE__,__FILE__,szDescription); \
}
For most of my particular use case, all I care about is logging debug information, so my exception has the file and line number in it along with an error message (message is optional as I have an assert without it as well). You could easily add FUNCTION or an error code of some type for better handling.
I can then use it like this:
int nRet = download_file(...);
APP_ASSERT_MSG(DownloadException == ERR_OK, "Download failed");
This makes error handling and reporting much easier.
For really nasty debugging, I used GCC's function instrumentation to keep a trace list of what's going on. It works well, but slows down the app quite a bit.
What I do regularly, FWIW, is not use exceptions, but rather explicit error handling in a standard format (ie: using a macro). For example:
result = DoSomething();
CHECK_RESULT_AND_RETURN_ON_ERROR( result );
Now, obviously, this has many limitations design-wise:
Your return codes may need to be somewhat uniform
Code is cluttered with macros
You may need many macros for various check conditions
The upside, though, is as Dummy00001 said: you can effectively generate a stack trace on demand in the event of a serious error, by simply caching the results. I also use this paradigm to log all unexpected error conditions, so I can adjust the code later to handle unexpected conditions which occur "in the wild".
That's my 2c.
Here's how I handle error reporting in my libraries (this may or may not suit your situation).
First, as part of your design you want a "core" or "system" library that all this common logic will sit in. All you other libraries will then link to the core and use its error reporting APIs, so your whole system has a single compact chunk of logic for handling this mess.
Inside the core, provide a set of logging MACROS such as "LogWarning" and "LogFatal" with documented behavior and expected usage- for example, LogFatal should trigger a hard abort of the process but anything lower than "LogError" is simply advisory (does nothing). The macros can provide a "printf" interface that automatically appends the "LINE", "FILE", and "FUNC" macros as arguments to the underlying singleton object that handles your error reporting.
For the object itself I'll defer to you. However, you want public APIs that configure your "drains"- e.g. log to stderr, log to logfile, log to MS Services log, etc. You also want the underlying singleton to be thread safe, re-entrant where possible, and very robust.
With this system, you can make "exception reporting" ONE MORE DRAIN TYPE. Just add an internal API to that error manager object that packages your logged message as an exception, then throws it. Users can then enable AND DISABLE exceptions-on-error behavior in your code with ONE LINE in their apps. You can put try-catches around 3rd party or system code in you libraries that then calls a "Log..." macro in the catch clauses to enable clean re-throw behavior (with certain platforms and compiler options you can even catch things like segfaults using this).

In C++ what are the benefits of using exceptions and try / catch instead of just returning an error code?

I've programmed C and C++ for a long time and so far I've never used exceptions and try / catch. What are the benefits of using that instead of just having functions return error codes?
Possibly an obvious point - a developer can ignore (or not be aware of) your return status and go on blissfully unaware that something failed.
An exception needs to be acknowledged in some way - it can't be silently ignored without actively putting something in place to do so.
The advantage of exceptions are two fold:
They can't be ignored. You must deal with them at some level, or they will terminate your program. With error code, you must explicitly check for them, or they are lost.
They can be ignored. If an error can't be dealt with at one level, it will automatically bubble up to the next level, where it can be. Error codes must be explicitly passed up until they reach the level where it can be dealt with.
The advantage is that you don't have to check the error code after each potentially failing call. In order for this to work though, you need to combine it with RAII classes so that everything gets automatically cleaned up as the stack unwinds.
With error messages:
int DoSomeThings()
{
int error = 0;
HandleA hA;
error = CreateAObject(&ha);
if (error)
goto cleanUpFailedA;
HandleB hB;
error = CreateBObjectWithA(hA, &hB);
if (error)
goto cleanUpFailedB;
HandleC hC;
error = CreateCObjectWithA(hB, &hC);
if (error)
goto cleanUpFailedC;
...
cleanUpFailedC:
DeleteCObject(hC);
cleanUpFailedB:
DeleteBObject(hB);
cleanUpFailedA:
DeleteAObject(hA);
return error;
}
With Exceptions and RAII
void DoSomeThings()
{
RAIIHandleA hA = CreateAObject();
RAIIHandleB hB = CreateBObjectWithA(hA);
RAIIHandleC hC = CreateCObjectWithB(hB);
...
}
struct RAIIHandleA
{
HandleA Handle;
RAIIHandleA(HandleA handle) : Handle(handle) {}
~RAIIHandleA() { DeleteAObject(Handle); }
}
...
On first glance, the RAII/Exceptions version seems longer, until you realize that the cleanup code needs to be written only once (and there are ways to simplify that). But the second version of DoSomeThings is much clearer and maintainable.
DO NOT try and use exceptions in C++ without the RAII idiom, as you will leak resources and memory. All your cleanup needs to be done in destructors of stack-allocated objects.
I realize there are other ways to do the error code handling, but they all end up looking somewhat the same. If you drop the gotos, you end up repeating clean up code.
One point for error codes, is that they make it obvious where things can fail, and how they can fail. In the above code, you write it with the assumption that things are not going to fail (but if they do, you'll be protected by the RAII wrappers). But you end up paying less heed to where things can go wrong.
Exception handling is useful because it makes it easy to separate the error handling code from the code written to handle the function of the program. This makes reading and writing the code easier.
return an error code when an error condition is expected in some cases
throw an exception when an error condition is not expected in any cases
in the former case the caller of the function must check the error code for the expected failure; in the latter case the exception can be handled by any caller up the stack (or the default handler) as is appropriate
Aside from the other things that were mentioned, you can't return an error code from a constructor. Destructors either, but you should avoid throwing an exception from a destructor too.
I wrote a blog entry about this (Exceptions make for Elegant Code), which was subsequently published in Overload. I actually wrote this in response to something Joel said on the StackOverflow podcast!
Anyway, I strongly believe that exceptions are preferable to error codes in most circumstances. I find it really painful to use functions that return error codes: you have to check the error code after each call, which can disrupt the flow of the calling code. It also means you can't use overloaded operators as there is no way to signal the error.
The pain of checking error codes means that people often neglect to do so, thus rendering them completely pointless: at least you have to explicitly ignore exceptions with a catch statement.
The use of destructors in C++ and disposers in .NET to ensure that resources are correctly freed in the presence of exceptions can also greatly simplify code. In order to get the same level of protection with error codes you either need lots of if statements, lots of duplicated cleanup code, or goto calls to a common block of cleanup at the end of a function. None of these options are pleasant.
Here's a good explanation of EAFP ("Easier to Ask for Forgiveness than Permission."), which I think applies here even if it's a Python page in Wikipedia. Using exceptions leads to a more natural style of coding, IMO -- and in the opinion of many others, too.
When I used to teach C++, our standard explanation was that they allowed you to avoid tangling sunny-day and rainy-day scenarios. In other words, you could write a function as if everything would work ok, and catch the exception in the end.
Without exceptions, you would have to get a return value from each call and ensure that it is still legitimate.
A related benefit, of course, is that you don't "waste" your return value on exceptions (and thus allow methods that should be void to be void), and can also return errors from constructors and destructors.
Google's C++ Style Guide has a great, thorough analysis of the pros and cons of exception use in C++ code. It also indicates some of the larger questions you should be asking; i.e. do I intend to distribute my code to others (who may have difficulty integrating with an exception-enabled code base)?
Sometimes you really have to use an exception in order to flag an exceptional case. For example, if something goes wrong in a constructor and you find it makes sense to notify the caller about this then you have no choice but to throw an exception.
Another example: Sometimes there is no value your function can return to denote an error; any value the function may return denotes success.
int divide(int a, int b)
{
if( b == 0 )
// then what? no integer can be used for an error flag!
else
return a / b;
}
The fact that you have to acknowledge exceptions is correct but this can also be implemented using error structs.
You could create a base error class that checks in its dtor whether a certain method ( e.g. IsOk ) has been called. If not, you could log something and then exit, or throw an exception, or raise an assert, etc...
Just calling the IsOk on the error object without reacting to it, would then be the equivalent of writing catch( ... ) {}
Both statement would display the same lack of programmer good will.
The transport of the error code up to the correct level is a greater concern. You would basically have to make almost all methods return an error code for the sole reason of propagation.
But then again, a function or method should always be annotated with the exceptions it can generate. So basically you have to same problem, without an interface to support it.
As #Martin pointed out throwing exceptions forces the programmer to handle the error. For example, not checking return codes is one of the biggest sources of security holes in C programs. Exceptions make sure that you handle the error (hopefully) and provide some kind of recover path for your program. And if you choose to ignore an exception rather than introduce a security hole your program crashes.