Exception safety when assigning an array using a post-injection 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 safe exception and neutral exception. This is achieved with a helper function NewCopy()

(which copies the specified memory and returns a pointer to the copied values) that follows these principles (we are not interested here, it is safe and neutral). Finally everything works because

  v_[vused_] = t;
  vused++;

      

we only change the state of the stack if the assignment doesn't throw. If we wrote

  v_[vused_++] = t;

      

Will exception safety be compromised? I think yes (postincrement operator returns the old value, but makes variable increments to ), and then, after her return to assign, so in case of an exception state object is invalid). But I could be wrong (?)

+3


source to share


3 answers


C ++ Standard n3337 ยง 8.3.4 / 6 Arrays

Note. Except when it was declared for a class (13.5.5), the index operator [] is interpreted so that E1 [E2] are identical to * ((E1) + (E2)). Due to the to + conversion rules applied, if E1 is an array and E2 is an integer, then E1 [E2] refers to the E2th member of E1. Therefore, despite its asymmetrical appearance, subscriptip is a commutative operation.

Thus,

v_[vused_++] = t;

      



does not differ from

*((v_)+(vused_++)) = t;

      

so it is clear what ++

will evaluate before assignment and in case of an exception being thrown T::operator=

, the state of the object is invalid.

+1


source


From the 1.9 / 15 standard:

When a function is called (regardless of whether the function is inline), each value evaluation and side effect associated with any argument expression or postfix expression that denotes the called function is sequenced before each expression or statement is executed in the body of the called function.

So when we call:

v_[vused_++]

      

i.e:



*(v_ + vused_++)

      

The postincrement statement is sequenced before dereferencing. So no matter what happens in that call, even if it throws, vused_

will increase. Thus, it would violate the strong guarantee of exclusion if the subsequent assignment is chosen incrementally vused_

. It's 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 called it foo(ctr); ctr++

, it would print 0.

+2


source


Considering NewCopy has a strong guarantee, then the code you have is exception safe.

Agree with @Barry that making the change you suggest will make it an unsafe exception (and won't provide better performance).

Yet again,

Why are you writing your own stack, then the standard library contains a fully optimized, throw-out implementation?

0


source







All Articles