4

I have a class similar to vector that is primarily a dynamically sized array. I am writing it for a resource-limited platform so I am required to not use exceptions.

It has become clear that to use operator overloading to simplify the interface for this class dynamic allocation would have to be performed in some of the operator overload functions. The assignment operator (=) is one example.

Without exceptions though, it becomes rather challenging to inform the caller of a bad allocation error in a sensible way while still retatining strong error safety. I could have an error property of the class which the caller must check after every call that involves dynamic allocation, but this seems like a not-so-optimal solution.

EDIT:

This is the best idea I have got at the moment (highlighted as a not-so-optimal solution in the paragraph above), any improvements would be greatly appreciated:

dyn_arr & dyn_arr::operator=(dyn_arr const & rhs) {
    if (reallocate(rhs.length)) // this does not destroy data on bad alloc
       error |= bad_alloc; // set flag indicating the allocate has failed
    else {
        size_t i;
        for (i = 0; i < rhs.length; ++i) // coppy the array
            arr[i] = rhs.arr[i]; // assume this wont throw an exceptions and it wont fail
    }
    return *this;
}

then to call:

dyn_arr a = b;
if (a.error)
  // handle it...

I havn't compiled this so there might be typos, but hopefully you get the idea.

jayjay
  • 1,017
  • 1
  • 11
  • 23
  • 1
    Without any exception throwing, *strong exception safety* becomes a *strong error safety*... – Jarod42 Aug 22 '14 at 12:30
  • 1
    And in your example, you forget to check if `arr[i] = rhs.arr[i]` succeeds... – Jarod42 Aug 22 '14 at 12:35
  • Thanks for your comment. Assuming arr[i] is of primitive type, say unsigned int, I wasnt aware it needed checking. If it is not a primitive type, you have hit the crux of my question, how is that checking usually done? – jayjay Aug 22 '14 at 12:42
  • This problem was not so clear in the question... For generic container/algorithm, you have to specialize if it is a *no error* type (as `int` or class which guaranty no error (à la `noexcept`)) or a class which has to manage error... – Jarod42 Aug 22 '14 at 12:50
  • I should have said in the question that `arr` was assumed to be an array of no error types. It is now edited to that effect. I did not word the question to hightlight the problem in this location, but it is the same problem (checking an assignment operator of a non primitive type) that the question is all about. – jayjay Aug 22 '14 at 12:55
  • Is exiting the program on `bad_alloc` an option? – R Sahu Aug 22 '14 at 15:06
  • @R Sahu unfortunately not. – jayjay Aug 22 '14 at 15:11

2 Answers2

3

Operator overloading has nothing to do with exceptions, it is simply allowing a "function" to be invoked by means of use of operators.

e.g. if you were writing your own vector you could implement + to concatenate two vectors or add a single item to a vector (as an alias to push_back())

Of course any operation that requires assigning more memory could run out of it (and you would get bad_alloc and have to manage that if you cannot throw it, by setting some kind of error state).

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • I'm not sure I was clear, there can be NO exceptions in my program, I am not using a throwing `new` call anywhere, rather a custom allocator. – jayjay Aug 22 '14 at 10:07
  • and I am aware that operator overloading has "nothing to do with exceptions". But it just so happens that if your operator overload function fails, exceptions are the only decent way (I know of) of passing an error back to the caller. – jayjay Aug 22 '14 at 18:28
3

There are two separate issues going on here.

The first is related to operator overloading. As CashCow mentions, overloaded operators in C++ are just syntactical sugar for function calls. In particular, operators are not required to return *this. That is merely a programming convention, created with the intention to facilitate operator chaining.

Now, chaining assignment operators (a = b = c = ...) is quite a corner case in C++ applications. So it's possible that you're better off by explicitly forbidding the users of your dyn_arr class to ever chain assignment operators. That would give you to the freedom to instead return an error code from the operator, just like from a regular function:

error_t operator = (dyn_arr const & rhs) {
    void *mem = realloc(...);
    if (mem == NULL) {
        return ERR_BAD_ALLOC; // memory allocation failed
    }
    ...
    return ERR_SUCCESS; // all ok
}

And then in caller code:

dyn_arr a, b;
if ((a = b) != ERR_SUCCESS) {
    // handle error
}

The second issue is related to the actual example you're giving:

dyn_arr a = b;

This example will NOT call the overloaded assigment operator! Instead, it means "construct dyn_arr object a with b as argument to the constructor". So this line actually calls the copy constructor of dyn_arr. If you're interested to understand why, think in terms of efficiency. If the semantics of that line included calling the assignment operator, the runtime system would have do two things as result of this line: construct a with some default state, and then immediately destroy that state by assigning to a the state of b. Instead, just doing one thing - calling the copy construction - is sufficient. (And leads to the same semantics, assuming any sane implementations of copy constructor and the assignment operator.)

Unfortunately, you're right to recognize that this issue is hard to deal with. There does not seem to be a really elegant way of handling failure in constructor, other than throwing an exception. If you cannot do that, either:

  • set a flag in the constructor and require/suggest the user to check for it afterwards, or
  • require that a pointer to already allocated memory area is passed as an argument to the constructor.

For more details, see How to handle failure in constructor in C++?

Community
  • 1
  • 1
kfx
  • 8,136
  • 3
  • 28
  • 52