2

During some implementation I stuck with the problem about exception safety guarantee and using std::move(). I know that SO is not a good place to ask "what is your opinion" (class template Boo) kind of a question but here I would like to make sure if my understanding is correct and after you read this question maybe you can direct me to the right path.

Consider following code:

struct Foo {
    using Y = std::vector<std::string>;
    using X = std::vector<Y>;

    void add (Y y) {
        src.push_back (std::move (y));      // (1)
        src = process (std::move (src));    // (2)         
    }

private:
    X process (X&& vec) {                   // (F)
        for (auto& v : vec) {
            v.resize (10);                  // (3)
        }
        return std::move (vec);
    }


    X src;
};

Take a look at the add() member function, if an exception is thrown from (1) and according to the n4296 [vector.modifiers/1]

If an exception is thrown while inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible<T>::value is true, there are no effects.

then strong guarantee is preserved, am I right?

If we look at the (2) then exception can also be thrown here, because (3) can throw an exception. Correct me if I am wrong: In this situation (2) if I move src to the process() member function and exception is thrown from process() then src is unspecified and that means that only basic guarantee is preserved?

To make add() member function strong guarantee should I change (2) and (F) respectively to:

src = process (src); // (2)
X process (X vec);   // (F)

?

So if Foo::src has huge number of elements, then making strong guarantee is associated with less efficiency (because extra copy has to be made) ?

Maybe guarantee level should be left to decide by the user of the class, by making it a class template, similar to this:

struct S {};
struct B {};

template <typename T>
struct Boo {
    using Y = std::vector<std::string>;
    using X = std::vector<Y>;

    void add (Y y) {
        src.push_back (std::move (y));
        src = process_helper (typename std::is_same<T, S>::type {});
    }


private:
    X process_helper (std::true_type) {
        return process (src);
    }

    X process_helper (std::false_type) {
        return process (std::move (src));
    }

    X process (X vec) {
        for (auto& v : vec) {
            v.resize (10);                  
        }
        return std::move (vec);
    }

    X src;
};

Is it a good or bad idea?

Artur Pyszczuk
  • 1,920
  • 1
  • 16
  • 23
  • 3
    `process` indirectly does `src = std::move(src)` [which is a problem](https://stackoverflow.com/questions/13127455). – nwp May 02 '16 at 06:44
  • @nwp: I don't think so: It first move constructs a temporary. – MikeMB May 02 '16 at 07:00
  • Why not `X process (X&& vec_in) { auto vec = std::move(vec_in);`, or just `X process (X vec)`? Ah, because the call strips out your src. Really to fix it you want `src.push_back (std::move (y));` to "process' `y` before inserting, or keep track of how to invert everything (using no exception checks to reduce overhead)? – Yakk - Adam Nevraumont May 02 '16 at 07:08
  • @Yakk I didn't mention the whole idea which stands behind the `resize()` call, because I didn't think it is worth, but if you suggested solution to process `y` before inserting, I have to worry you (or me), I can not do this, because I want to have all `src` rows to have exactly the same length, which means if I pass `y` which have different (greater) size than any row in the `src` then I have to take action to resize rest of them to keep them equal (to y). Reverting after exception can still be an option. – Artur Pyszczuk May 02 '16 at 07:59
  • Personally: As long as you are not writing a general purpose library, I'd have a closer look at the calling code and see, whether it requires the strong exception gurantee or not and decide for one variant accordingly. In particular, it is rare that an application actually proceeds execution, after a std::bad_alloc is thrown. – MikeMB May 02 '16 at 08:25
  • Note that this question is **not** about `std::move()`; it's about passing rvalues to functions. `std::move()` is one way of getting rvalues, but it's not the only way. It's just a fancy cast, and doesn't do any data modification. – Pete Becker May 02 '16 at 12:17
  • If move (construction) can throw, and you need capacity increase, there is no way to generate strong exception guarantee without copies. – Yakk - Adam Nevraumont May 02 '16 at 12:58
  • Can you explain what the point of all this is. E.g. are you trying to atomically add an item to a vector and resize all items. – M.M May 02 '16 at 14:06

0 Answers0