Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception safety in array assignment using postincremented index

In this code:

template<class T>
void Stack<T>::Push( const T& t )
{
  if( vused_ == vsize_ )                           // grow if necessary
  {
    size_t vsize_new = vsize_*2+1;                 // by some grow factor
    T* v_new = NewCopy( v_, vsize_, vsize_new );
    delete[] v_;                                   // this can't throw
    v_ = v_new;                                    // take ownership
    vsize_ = vsize_new;
  }
  v_[vused_] = t;
  vused++;
}

we try to be exception safe and exception neutral. It is achieved by a means of having NewCopy() helper function (that copies pointed memory and returns a pointer to a copied values) which follows these principles (we are not interested in that function here, it is exception safe & neutral). Lastly, all works because

  v_[vused_] = t;
  vused++;

we only change state of Stack if assignment does not throw. If we wrote

  v_[vused_++] = t;

would the exception safety be violated? My guess is yes (postincrement operator returns old value but it does increment variable before return, and then after it returned the assignment is performed, so in case of exception the object state is invalid). But I might be mistaken (?)

like image 983
4pie0 Avatar asked Sep 02 '25 16:09

4pie0


1 Answers

From the standard, 1.9/15:

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.

So when we call:

v_[vused_++]

which is to say:

*(v_ + vused_++)

The postincrement operator is sequenced before the dereference. Thus, regardless of what happens in that call, even if it throws, vused_ will be incremented. So this will violate the strong exception guarantee if the subsequent assignment throws since vused_ will be incremented. This is easy to convince yourself:

void foo(int ) { throw std::runtime_error(""); }

int main() {
    int ctr = 0;
    try {
        foo(ctr++);
    }
    except(...) { }

    std::cout << ctr << std::endl; // prints 1
}

But if we had called foo(ctr); ctr++, it'd print 0.

like image 190
Barry Avatar answered Sep 05 '25 04:09

Barry