1

I'm implementing a simple vector like std::vector, and I wrote some functions , without worrying what kind of exception safe guarantee it gives. I know a little about exceptions of c++, but i have not experienced writing exception safe codes. Here is my code :

template <typename T>
class vector {
public:
   vector(int _DEFAULT_VECTOR_SIZE = 10) :
       size(0), array_(new T[_DEFAULT_VECTOR_SIZE]), capacity(_DEFAULT_VECTOR_SIZE)  {}
  void push_back(T& elem)
  {
    if (size == capacity)
    resize(2 * size);
    array_[size] = elem;
    size++;
  }

  void pop_back()
  {
     --size;
  }

  void resize(int size_)
  {
     if (size_ > capacity)
    {
      T* temp = new T[size_];
      memcpy(temp,array_,size*sizeof(T));
      swap(temp, array_);
      delete[] array_; 
      capacity = size_;
     }
  }

  private:
    T* array_; 
    int size;
    int capacity;
 };

So my question is: How can I modify my code(functions) , that would give at least basic guarantee , or some technique of writing exception safe code for basic or strong guarantee ? Thanks

fitzbutz
  • 956
  • 15
  • 33
Seno Alvrtsyan
  • 241
  • 1
  • 11
  • `try { /* code that throws exceptions */ }catch{ /* handle exceptions */ }`, as far as I remember – ForceBru Feb 25 '15 at 13:45
  • 4
    I hope you realize how wrong this is: `memcpy(temp,array_,size*sizeof(T));` – tenfour Feb 25 '15 at 13:47
  • 1
    why memcpy(temp,array_,size*sizeof(T)) wrong ? – Seno Alvrtsyan Feb 25 '15 at 13:48
  • `_DEFAULT_VECTOR_SIZE` is an [implementation reserved identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier). Also, there is no simple implementation of `std::vector`. – Baum mit Augen Feb 25 '15 at 13:48
  • 1
    @SenoAlvrtsyan `memcpy` will only work for POD types like `int`, or very simple classes. See also http://stackoverflow.com/questions/17810174/why-isnt-memcpy-guaranteed-to-be-safe-for-non-pod-types – tenfour Feb 25 '15 at 13:55
  • As "ForceBru" says - use try{}catch{} blocks. Understand where something could happen in your code that would throw an error (e.g. passing a negative value that will later be used as the key for an array) and ensure anything like that is enclosed in a try{}catch{} block. Then you can examine the error inside the catch part and handle it as you see fit. – Kvothe Feb 25 '15 at 13:59
  • @Kvothe: Actually, you hardly need `try{ } catch{ }` at all, and you certainly don't need to examine the exception. The reason you hardly need `catch{ }` is because C++ will run dtors of local variables for you even if an exception happens. So you let local variables manage each resource that need cleanup in case of an exception. – MSalters Feb 25 '15 at 14:29
  • @MSalters How would you handle the case where the constructor of a class initialises some of its values by reading a file but if the format of the file is wrong or doesn't exist you want it to stop and tell the user? I've been using `try{}catch{}` for this - should I be doing something else? – Kvothe Feb 25 '15 at 14:34
  • @Kvothe: Oh, that's an entirely different case than a container class. The problem is that a container class has no idea what kind of exception could be thrown by the element type. In your example, you do know what exception to expect and where to handle it. That's the correct use of `try { dangerous() } catch (expected& it) { handle(it); }` – MSalters Feb 25 '15 at 14:38
  • Is it good idea to use std::copy in my vector's resize function ? – Seno Alvrtsyan Feb 25 '15 at 20:50
  • 1
    @SenoAlvrtsyan: Short answer: yes. But you could expand that into a decent new question (do show your `.resize` implementation), and explain in that question that you're especially wondering about the exception safety of `std::copy` _as you use it_ . – MSalters Feb 26 '15 at 17:31

1 Answers1

1

Exception-safety comes in two major flavours:

  1. If an exception happens, your program ends in a sane state. Which one is unspecified.
  2. If an exception happens, your program ends in the original state.

The main challenge for you will be to deal with assignment and copy constructors throwing. As the comments already note, you shouldn't use memcpy because that fails to call copy constructors. Copying a std::string should also copy the character buffer, for instance, and a vector of strings is a perfectly normal type which you should support.

So, let's look at your vector's copy constructor. It will need to copy every element of the source vector. And each individual copy can throw. What if one of the strings is so long that a copy throws std::bad_alloc ?

Now, exception safety means that you leave the program in a sane state, so no memory leaks. Your vector's copy ctor failed, so the dtor won't run. Who cleans up T* array then? This must be done in your copy ctor.

When the copy fails, there won't be a new vector, so you get the second type of exception safety for free. ("strong exception safety"). But let's look at the assignment operator next, v2 = v1. There's an old vector that you will overwrite. If you first do a .resize(0) and then copy over all elements, you may encounter an exception halfway through the copy. Your original vector content is gone, and the new content is incomplete. Still, you haven't leaked any memory yet nor have you copied half an element.

To make the assignment safe, there's a simple trick: first copy the source vector to a temporary vector. If this fails, no problem (see above). We didn't touch the destination yet. But if the assignment succeeds, we swap the array* pointers, size and capacity of the temporary and the destination vectors. Swapping pointers and swapping ints is safe (can't throw). Finally, we let the temporary vector go out of scope which destroys the old vector elements that are no longer needed.

So, by doing all the dangerous operations on temporaries, we ensure that any exception doesn't touch the original state.

You'll need to check all your methods to see if these problems can occur, but the pattern is usually similar. Don't leak array if an element copy or element assignment throws, do propagate that exception to your caller.

MSalters
  • 173,980
  • 10
  • 155
  • 350