One is to use C++ exceptions: try catch blocks. But freeing dynamic memory will be an issue when an exception is raised.
Second is to use C style: errno variable
Third is just to return -1 on error and 0 on success :)
Which way should be chosen for a mid-size project and why? Any other better approach..?
But freeing dynamic memory will be an issue when an exception is raised.
No it's not. std::vector<int> v(100);
Done.
The concept here is called Scope-Bound Resource Management (SBRM), also known by the much more common (and awkward) name Resource Acquisition Is Initialization (RAII). Basically, all resources are contained in some object which will clean up the resource in the destructor (which is always guaranteed to be run for an automatically allocated object). So whether or not the function exists normally or via exception, the destructor is run and your resource is cleaned up.
Never do an allocation where you need to free it explicitly, use containers and smart pointers.
Second is to use C style: errno variable
Third is just to return -1 on error and 0 on success :)
And how do they help solving your problem of freeing dynamic memory? They also use an early-exit strategy, same as throw
.
So in summary, they don’t have an advantage over C++ exceptions (according to you).
In the first place, you should strive for a program with minimum error cases. (Because errors are not cool.)
Exceptions are a nice tool but should be used conservatively: reserve them for "exceptional cases", do not use them to control the flow of your program.
For example, do not use exceptions to test whether a user input is correct or not. (For such a case, return an error code.)
One is to use C++ exceptions: try catch blocks. But freeing dynamic memory will be an issue when an exception is raised.
@see RAII.
Exceptions should be your preferred method of dealing with exceptional runtime situations like running out of memory. Note that something like std::map::find doesn't throw (and it shouldn't) because it's not necessarily an error or particularly exceptional case to search for a key that doesn't exist: the function can inform the client whether or not the key exists. It's not like a violation of a pre-condition or post-condition like requiring a file to exist for a program to operate correctly and finding that the file isn't there.
The beauty of exception-handling, if you do it correctly (again, @see RAII), is that it avoids the need to litter error-handling code throughout your system.
Let's consider a case where function A calls function B which calls C then D and so on, all the way up to 'Z'. Z is the only function that can throw, and A is the only one interested in recovering from an error (A is the entry point for a high-level operation, e.g., like loading an image). If you stick to RAII which will be helpful for more than exception-handling, then you only need to put a line of code in Z to throw an exception and a little try/catch block in A to catch the exception and, say, display an error message to the user.
Unfortunately a lot of people don't adhere to RAII as strictly as they should in practice, so a lot of real world code has more try/catch blocks than should be necessary to deal with manual resource cleanup (which shouldn't have to be manual). Nevertheless, this is the ideal you should strive to achieve in your code, and it's more practical if it's a mid-sized project. Likewise, in real world scenarios, people often ignore error codes returned by functions. if you're going to put the extra mile in favor of robustness, you might as well start with RAII because that will help your application regardless of whether you use exception handling or error code handling.
There is a caveat: you should not throw exceptions across module boundaries. If you do, you should consider a hybrid between error codes (as in returning error codes, not using a global error status like errno) and exceptions.
It is worth noting that if you use operator new in your code without specifying nothrow everywhere, ex:
int* p = new int(123); // can throw std::bad_alloc
int* p = new(std::nothrow) int(123); // returns a null pointer on failure
... then you already need to catch and handle bad_alloc exceptions in your code for it to be robust against out of memory exceptions.
Have a look at this comment by Herb Sutter on try catch for C++ GOTW. And do go through his whole set of articles. He does have a lot to say on when and how to check and save yourself from error conditions and how to handle them in the best ways possible.
Throw an exception. Destructors of variables are always called when an exception is thrown, and if your stack-based variables don't clean up after themselves (if for example you used a raw pointer when you need to delete the result), then you get what you deserve. Use smart pointers, no memory leaks.
But freeing dynamic memory will be an issue when an exception is raised.
Freeing memory (or any other resource for that matter) doesn't suddenly become a non-issue because you don't use exceptions. The techniques that make dealing with these problems while exceptions can be thrown easy, also make it easier when there can be "error conditions".
Exceptions are good for passing control from one context to another.
You let the compiler do the work of unrolling the stack between the contexts then in the new context compensate for the exception (and then hopefully continue).
If your error happens and can be corrected in the same context then error codes are a good method to do error handling and clean up (Don't take this to mean you should not be using RAII you still need that). But for example within a class an error occurs in a function and the calling function can correct for that type of error (then it probably is not an exceptional circumstance so no exceptions) then error code are useful.
You should not use error codes when you have to pass information out of a library or sub system as you are then relying on the developer using the code to actually check and handle the code to make sure it works correctly and more often than not they will ignore error codes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With