5

Probably, a lame question, but I keep failing to find comprehensive answer.

Parameters of std::vector::emplace_back are r-value references. As far as I understand, it is unsafe to use object after it was passed somewhere by r-value reference. I mean following:

std::string str("hello world");
std::string str2(std::move(str)); // string::string(string &&);
cout << str;                      // unsafe, str was moved to str2

So, what will happen in following example?

std::vector<std::string> array;
std::string str("hello world");   // what if add 'const' qualifier here?
array.emplace_back(str);          // template <class... Args>
                                  // void emplace_back (Args&&... args);
std::cout << str;                 // safe or not? str was moved or copied?

I'm really confused here. My tests shows that,str is safe to use after emplace_back, but my (broken?) logic tells me that str was moved and shouldn't be used after that.

PS. Sorry for my bad English :)

J. Doe
  • 429
  • 2
  • 12
  • 1
    I would like to note, that `emplace_back`is a 'delegating constructor', so you don't just have to send it the type in question, you could send it any parameters that the objects constructor would take, with `vector` for example you could do `emplace_back(7,'c')`. If you already have a variable of the type in question, `push_back()` is more idiomatic. – sp2danny Apr 03 '17 at 21:21
  • see http://stackoverflow.com/questions/4303513/push-back-vs-emplace-back – sp2danny Apr 03 '17 at 23:26

3 Answers3

11

The parameters for emplace-style functions are forwarding references, which means they become lvalue references for lvalue arguments and rvalue references for rvalue arguments.

With

array.emplace_back(str);

str is an lvalue (you have not cast it to an rvalue with std::move) so it will be copied. It will retain its value after the call.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Also, using `str` in the first example should also be "safe". – juanchopanza Apr 03 '17 at 20:48
  • only if you don't care about its value – Brian Bi Apr 03 '17 at 20:53
  • 1
    moved-from objects are only safe do destruct, unless their documentation says otherwise. the documentation for `std::string` says 'unspecified but valid' – sp2danny Apr 03 '17 at 20:59
  • 1
    @sp2danny Where does it say that? You can do a lot more than that with a moved from `std::string`. – juanchopanza Apr 03 '17 at 21:04
  • It was not meant as a standards quote, but a recommendation. The implementer of the move operator can, and often will, make moved-from objects unusable. Most standard library types offer better guarantees, and document those. – sp2danny Apr 04 '17 at 10:24
2

A standard library object will generally be in a "valid but unspecified state".

valid but unspecified state
a value of an object that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type [Example: If an object x of type std::vector<int> is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called only if x.empty() returns false. —end example]

Most often this means either empty, or retaining the original value. Moving an int probably doesn't reset its value.

Some types are more specified, for example unique_ptr always holds a nullptr after being moved from.

So, in this case

std::string str("hello world");
std::string str2(std::move(str)); // string::string(string &&);
cout << str;   

the code is valid, but we don't know exactly what the output will be, if any. Makes it less than useful.

The idea is that you should let the variable go out of scope after being moved from, or assign it a new value to use it further.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • this is useful information, but does not actually answer the question, since `emplace_back()` won't move from an lvalue. – sp2danny Apr 03 '17 at 21:09
  • Ok, I might have answered only the first part of the question, where there *is* a move, and not specifically about `emplace_back`. – Bo Persson Apr 04 '17 at 13:40
1

emplace_back will copy l-values, and move r-values.

One might test this with a simple example:

struct test {
    test() {
        std::cout << "Default-constructed\n";
    }

    test(test const&) {
        std::cout << "Copy-constructed\n";
    }

    test(test &&) {
        std::cout << "Move-constructed\n";
    }

    ~test() {
        std::cout << "Destructed\n";
    }

    test& operator=(test const&) {
        std::cout << "Copy-assigned\n";
        return *this;
    }

    test& operator=(test &&) {
        std::cout << "Move-assigned\n";
        return *this;
    }
};

int main() {
    std::vector<test> vector;
    test t;
    vector.emplace_back(t);//The important one
    vector.emplace_back(test{});
    return 0;
}

This (should, assuming copy-ellision doesn't apply here) result in the following output:

Default-constructed
Copy-constructed //The important one
Move-constructed
Destructed
Destructed
Destructed

Note that when emplace_back was called with an l-value, the copy-constructor was called. So in your case, the string would be copied, not moved, and therefore safe to keep using outside the vector.

It's also worth noting that Move-Semantics usually require that a moved-from object be "in an unspecified, but valid, state", which means it's actually not supposed to be "unsafe" to use a moved-from object. It can still have weird effects though, and can invoke undefined behavior depending on what a valid state for that object can encompass (like, for example, if you try to dereference a moved-from unique_ptr or other similar object).

Xirema
  • 19,889
  • 4
  • 32
  • 68
  • move semantics only _require_ that the object can be destroyed. **standard library types** often give out more guarantees. – sp2danny Apr 03 '17 at 21:10