views:

138

answers:

6

Hi,

I am currently starting to look into operator overloading in c++ for a simple 2D vertex class where the position should be available with the [] operator. That generally works, but I dont really know how to deal with errors for instance if the operator is out of bounds (in the case of a 2D vertex class which only has x and y values, it is out of bounds if it is bigger than one)

What is the common way to handle errors in cases like that?

Thanks

+4  A: 

When you have to throw, you have to throw. There's no other way to diagnose a problem in an overloaded operator unless you can return some sort of magic exploding result value. Define an exception type, throw it on errors, document it.

bmargulies
+2  A: 

According to the C++ language FAQ, operator[] should not be used for matrices or 2d array implementations; instead use operator().Click here for FAQ #13.10

The big problem is implementing [] for multiple dimensions.

As for errors, you will have to go to the exception route if you don't want to provide any extra parameters to your overloaded operator (another reason to use operator().

Thomas Matthews
I think he means a vector with 2 elements (i.e. a point in 2D space), not a 2D matrix.
Adam Rosenfield
exactly! Thanks, i will look into that.
moka
+1  A: 

I think an assertion might also be in place. Do you foresee that it would ever be anything else than a (simple?) programmer's error to go out of bounds in a 2d vector?

T& operator[](size_t index)
{
    assert(index < 2 && "Vector index out of bounds");
    return pos[index];
}

If you are going to throw exceptions, I suppose you could also use out_of_range - or a type derived from it.

UncleBens
+1  A: 

As others have pointed out, exceptions are the way to go.

But that would seem to be quite an unusual idiom for accessing a point class like that. I would find it much more straightforward for the vertex class to have separate members:

class Vertex {
    ...
    double x;
    double y;
};

Then you can operate on them by doing things like vertex1.x - vertex2.x etc, which IMO is more readable than vertex1[0] - vertex2[0]. For an added bonus it avoids your exception problem completely.

Peter
well, I want that they are accessible with both. you can do vertex1.x()-vertex2.x() or vertex[0]-vertex2[0]. basically I want to have the data in one array and add different ways of accessing it
moka
+1  A: 

You have at least two options other than exceptions for handling indexes out of bounds:

  • Just trust your input, document that it's undefined behaviour to use an index out of bounds, and rely on your callers to be professionals[*].
  • Abort if an index is out of bounds, by calling std::terminate(), or abort() directly, or whatever, perhaps after printing an error message.

There's a compromise between the two, which is to use the assert macro. This will do the former in release builds (compiled with NDEBUG), and the latter in debug builds.

Not that exceptions are necessarily a bad idea, but they have their problems. Then again, most of those problems go away if you never catch them.

In this case, the caller has to pass you either 0 or 1. If they sometimes pass you 2, and plan to catch the exception that happens when they do, then there may be no hope for them. Don't spend too much time worrying about it.

Another option would be to accept all inputs, but map them on to one or other of the values. For instance you could bitwise-and the input with 1. This makes your code very simple, with the obvious disadvantage that it obscures other people's bugs.

[*] Not to say that professionals don't make mistakes. They do. They just don't expect you to save them from their mistakes.

Steve Jessop
+1  A: 

Error handling is a tricky beast in the best of times. It pretty much boils down to how big a deal the error is, and what if anything is expected to happen with it when it occurs.

There are four basic paths you can follow:

  1. Throw an exception
    • The sledgehammer of error handling. A great tool, definitely want to use it if you need it, but if you're not careful you'll end up smashing yourself in the foot.
    • Essentially skips everything between the throw and the catch, leaving nothing but death and destruction in it's wake.
    • If it's not caught, it will abort your program.
  2. Return a value that indicates failure
    • Leave it to the programmer to check for success and react accordingly.
    • Failure value would depend on type. Pointers can return NULL or 0, STL containers return object.end(), else otherwise unused values can be used (such as -1 or "").
  3. Process the condition gracefully
    • Sometimes, an error isn't really an error, just an inconvenience.
    • If useful results can still be provided, a mistake can easily be swept under the carpet without hurting anyone.
    • For example, an out of range error can just return the last variable in an array, without needing to resort to any of that messy exception stuff.
    • So long as it's predictable and defined, the programmer can make of it what they wish.
  4. Undefined behaviour
    • Hey, programmers shouldn't be giving you bad input in the first place. Let them suffer.

In general, I would resort to option one only for stuff that's program-breaking, for things that I don't really expect to recover from without concerted effort. Otherwise, using exceptions as a form of flow control is little better than going back to the days of goto.

Option two is probably the most common for non-program-breaking errors, but it's effectiveness really depends on the types of return you're dealing with. It is advantageous since it lets the programmer control the flow locally by detecting failures and recovering themselves. When dealing with overloading operators, it is of limited use, but I figured I'd throw it in for the sake of completeness.

Option three is very circumstance-specific. Many errors can't be handled in such a way, and even the ones that can can lead to unintuitive results. Use with caution, and be sure to document thoroughly. Or, don't document it at all, and pretend it's option four.


Now, as to the specific example provided, that being an out of range error on an overloaded operator[], I would personally go for option four. Not because I particularly enjoy watching other programmers suffer when they deal with my code (I do, incidentally, but that's tangential to the discussion), but because it's expected.

Most cases where a programmer would be using operator[], they expect to handle their own bounds checking and don't rely on the type or class to do anything for them. Even in the STL containers, you can see operator[] (no range checking) in parallel with the otherwise redundant object.at() (which does range checking). Reflecting the expected behaviour with your own overloaded operators tends to make for more intuitive code.

goldPseudo
okay, thanks, I will propably go with option four! Anyways your post was really helpful!
moka