Help Understanding try and catch

Hello! For some reason, I just can't wrap my head around this despite reading multiple articles.

I am implementing a circular linked list and within each node is an array. I want to test and make sure that the array is properly enqueuing and dequeuing properly among other edge cases (inserting/removing 1 item, inserting/removing everything, etc.) and return true/false.

I'm not understanding how to try/catch these errors.

So for example, if I wanted to test inserting/removal of one item (when the size of the array is 1000) , would it be something like:

1
2
3
4
try { inserting one value into the array} 
catch {no error} 
try {removing one value from the array}
catch {no error} 


Is there even a need to try/catch here since there shouldn't be any error if one item is inserted into an array with a size of 1000? )

But I'm not sure and I'm confused as to why I'd use try/catch on something that shouldn't make an error if that makes sense? I'd appreciate any help. Thank you so much.
Last edited on
A couple of simple examples using try/catch, including divide by zero:
https://www.guru99.com/cpp-exceptions-handling.html
I'm sure there are reading lists that include introductory materials from luminaries like Stroustrup, Sutter and the like.

You'll need to read them.

However, in this case I'll venture some advice.

The concept is called "exceptions". "Try" and "Catch" are merely keywords in the C++ implementation of exception handling.

As Stroustrup put it (I paraphrase), exceptions should be limited to exceptional situations.

Put another way, most good coding standards and tutorial materials point out that when an error is the natural result of attempting an operation, an exception may not be the best means of handling the error.

A bool return can suffice in such situations, and is faster, lighter and simpler to understand.

When a return value is a result (like getting an answer from a complex calculation), there may not be a return value to signal an error occurred attempting to perform the calculation. Like Furry Guy's "divide by zero" suggestion, that might be a situation where an exception is the best option when no other option is better.

Usually, or at least in the best examples, exceptions represent something has gone so wrong that you face the choice of terminating the program or correcting what is wrong so it can be retried.

If one is writing to a file, but the storage fills up, an exception may be a good way of responding to the situation, because if the user can free up space, the output can continue. Otherwise, the write may be abandoned and another destination selected. Yet, the code to write out a file may not easily be structured to return a failure value, so it could be that an exception is used to trigger a "solution" pathway.

In all cases of this example, though, it is unlikely that the problem means the program must be terminated.

When memory allocation errors happen, on the other hand, there can be such a catastrophic mess in memory that there is no solution to recovery, and an exception ends up merely being a way to inform the user that there is a problem, and the program is going to terminate (and probably to write error information in a log for the developers to ponder about fixing the design).

In each situation, the basic idea is to "throw" an exception if something is wrong.

The "catch" class will not execute unless an exception is thrown. This seems to conflict with you notation "catch { no error }". The catch is what happens when an error happens, and that is signaled by throwing an exception.

There is no "catch" without a throw, and there shouldn't be a "throw" unless there's an error.

Only the code within the "try" is being executed to completion under normal circumstances.

What is important to understand, too, is that C++ will guarantee all local allocations within the try will be freed, even if the catch ends up executing in response to a throw.

1
2
try { A a; dosomething( a ); }
catch(...){}


This code suggests some object of type A is being created. It could be complex, with its own allocated memory (like a string or a container). If "dosomething()" throws an exception, the catch will be executed (in this case a catch all due to the ... ), and, the instance of A ("a" in this case) will be properly destroyed. There won't be a memory or resource leak because "A" will be shut down "normally".

It is one of the key reasons we choose exceptions over error returns - that is, when the result of an error signal may involve a complex "shut down" of the prerequisites to the call, like A being required by dosomething().

We know from historical C examples that without this guaranteed "shutdown", handling errors is, itself, quite prone to bugs and causes lots of problems.

Handling exceptions isn't an easy subject. In some cases it can be clear how to respond to something obvious, like a drive filling up, but in others we are trying to deal with anything that might happen. When that becomes a long list of alternatives, it may hint that the only option is termination of the program, and likely a reconsideration of why the design gets into that situation in the first place.

One can, in theory, surround the entire execution of a program:

1
2
3
4
5
6
int main()
{
 try{ runprogram(); }
 catch(...){ cout << "Well, that didn't go well" << endl; }
}


The ellipsis matches any exception. However, there might be two obvious errors you might handle differently.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
 bool should_exit { false };

 while( ! should_exit )
 {

 try{ runprogram(); }
 
 catch( nofileexception & e ) { /* file wasn't found */ should_exit = false; }
 catch( invalidfile & e ) { /* file is not valid */ should_exit = false; }
 catch(...){ cout << "Well, that didn't go well" << endl; should_exit = true; }

 }

 return 0;
}


This shows that the programmer intends to throw two exception types, and that type indicates what error occurred. Each catch handler may do whatever that type of error implies.

In this case, should_exit is used to indicate if the program should try again, or terminate.


Overall, too, you may use libraries that throw exceptions in case of errors. You have little choice, unless the library has options to enable or disable exceptions, but to allow exceptions to propagate out of your code (to the code that called yours), or to handle exceptions in your code.

When a library throws an exception you can catch that exception to respond to it. If you don't, and the library throws an exception, only a matching or "catch all" exception above yours (that which called your code) will fire, and do whatever it does...or...if nothing catches that exception, the program terminates.

In those cases you may need to catch exceptions, but not necessarily at each use of the library.

That can be messy and cumbersome. It may also be overkill.

It may do to wrap a "try" and "catch" around a function, like a shell around your code, so that if you're performing several calls to a library that might throw exceptions, you're catch(es) will provide an overall indication of trouble that doesn't require handling at the call which generated it.

This is appropriate when you can assume there really should be a problem, as you indicated in your question, but you do know the library could throw exceptions, and you're not expecting them.

Responding to exceptions you expect not to happen is usually exceptional, and quite the reason for the exception mechanism in the first place.

Instead of thinking about exceptions as part of calling some function or performing some single step, it is more often appropriate to think of exceptions as an "net" in the sense of a gymnast's safety. Something "outside" the normal operation of things, that catches errors "outside" the arena you're working in.

On the other hand, there are some exceptions thrown by libraries as their primary error handling design, and we can argue (as we do) as to whether that's really a good idea (instead of returning an error value), but when such libraries are used, we have little choice but to consider wrapping each call to those functions which do this in a try/catch block. A lot depends on what the error is, how we can deal with it, if it is really that local to the call generating the error, and if correction is simple and likely to work.



Last edited on
Topic archived. No new replies allowed.