[STL Exception Safety] Nothrow guarantee for std::copy in special cases?

A class holds several Ts in a vector<T*>. A file loading routine loads new Ts which shall be added to the vector:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
clas X
{
  vector<T*> vt;
// ...
};

//do something with vt, it might be empty or not
void X::load(const std::string& filename)
{
  vector<T*> tmp;
  try
  {
    // load new T*s into tmp from a file
    vt.reserve(vt.size()+tmp.size());
  }
  catch(...)
  {
    // delete everything in tmp and return
  }
  // Might the next line throw?
  std::copy(tmp.begin(), tmp.end(), std::back_inserter(vt));
}

Obviously, the standard does not guaratee that copy or back_inserter won't throw. On the other hand: the standard describes the "general case". If vt.reserve() was called, neither copy nor back_inserter /should/ throw (no bad_alloc since vt is not allowed to reserve more memory in the meantime, no errors in the assignment operator of T*s (they are pointers)).

Might that last line throw? Why (or why not)?

(Note: there is a solution to the problem I have currently. Substitute the last line with:

1
2
3
4
5
6
7
8
9
10
11
try
{
std::vector<T*>::const_iterator beyond = tmp.end();
tmp.reserve(tmp.size()+vt.size());
std::copy(vt.begin(), vt.end(), std::back_inserter(tmp));
}
catch(...)
{
  // delete everything in tmp in the range [beyond, tmp.end())
}
std::swap(vt, tmp); // May *not* throw 

This works (only!) because beyond is guaranteed not to be invalidated, since reserve() was called. In my current problem, this solution is OK since it doesn't matter where the new elements go (before or after the old ones). However, this solution does something elese than the code above)

Any thoughts on that are appreciated.
An STL algorithm should not make such guarantees. Tools should not instruct their master.

The std::copy() algorithm is guaranteed not to throw only if the three iterators given to it are guaranteed not to throw. Notice how this implies a lot about the iterators and the containers themselves...

Frankly, if new or malloc() fail, your entire system is unstable and about to crash. Likewise, if your OS throws an exception it will crash your program anyway. But, that aside, why not keep everything you want protected in try..catch blocks? And why mess with a temporary anyway?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
...

template <typename T>
bool X::load( const std::string& filename )
  {
  try
    {
    ifstream file( filename );
    std::copy(
      std::istream_iterator <T> ( file ),
      std::istream_iterator <T> (),
      std::back_inserter( vt )
      );
    bool result = file.fail();
    file.close();
    return result;
    }
  catch (...)
    {
    return false;
    }
  }

This appends as many Ts as it can get from the file to vt and returns whether or not something went wrong (including if the file could not be opened).

If you really, really want vt to be guaranteed to have the same value if anything does go wrong, you can always
std::streamsize original_size = vt.size();
at the beginning of your function and
vt.erase( vt.begin() + original_size );
on failure.

Since everything is inside a catch-all try..catch construct, you can safely add a zero-throw list specifier to the header:
1
2
template <typename T>
void X::load( const std::string& filename ) throw()


Hope this helps.
Last edited on
Thanks for your reply.
1
2
3
std::streamsize original_size = vt.size();
// ...
vt.erase( vt.begin() + original_size );

Ouch, somtimes the solution is so simple... thanks.

If you really, really want vt to be guaranteed to have the same value if anything does go wrong

Yes, actually, I do. The file is provided by a user and I don't trust them (and the code is part of a library, and I don't want to make the host application crash because of some sloppy code). True, if an bad_alloc occurs, that won't matter much. Nonetheless I want the strong guarantee if I can get it without too much overhead (ressource-wise).
Yeah, I always feel extra stupid when the obvious knocks me upside the head.

There's nothing particularly wrong with using a temporary, because you can always append() it to the result if all goes well.

The try..catch blocks will catch everything C++ handles, including memory errors.

There isn't a whole lot you can (or should) do about segfaults, etc. On Windows you can use "Structured Exception Handling". Many Windows C++ compilers can automatically integrate SEH into the C++ exception system for you.

:-)
Topic archived. No new replies allowed.