Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to pass as argument a member of an object which is moving

#include <iostream>
#include <string>
#include <map>

struct A {
    int n { 42 };
    std::string s { "ciao" };
};

int main() {
    A a;
    std::map<std::string, A> m;
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: ciao"
    m.emplace(a.s, std::move(a)); // a.s is a member of a, moved in the same line
    std::cout << "in map: " << m.count("ciao") << std::endl; // print: "in map: 1"
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: " (as expected, it has been moved)
}

Is it safe to pass as an argument a member of a "moving" object? In this case, emplace seems to work: the map has the expected key.

like image 376
Alessandro Pezzato Avatar asked Dec 03 '14 08:12

Alessandro Pezzato


Video Answer


1 Answers

Interesting. I think it's safe, for convoluted reasons. (For the record, I also consider it very bad style -- an explicit copy costs you nothing here, since it will be moved into the map.)

First of all, the actual function call is not a problem. std::move only casts a to an rvalue reference, and rvalue references are just references; a is not immediately moved. emplace_back forwards its parameters to a constructor of std::pair<std::string, A>, and this is where things get interesting.

So, which constructor of std::pair is used? It has rather many, but two are relevant:

pair(const T1& x, const T2& y);
template<class U, class V> pair(U&& x, U&&y);

(See 20.3.2 in the standard), where T1 and T2 are the template arguments of std::pair. As per 13.3, we end up in the latter with U == const T1& and V == T2, which makes intuitive sense (otherwise moving into a std::pair would be effectively impossible). This leaves us with a constructor of the form

pair(const T1& x, T2 &&y) : first(std::forward(x)), second(std::forward(y)) { }

as per 20.3.2 (6-8).

So, is this safe? Helpfully, std::pair is defined in some detail, including memory layout. In particular, it states that

T1 first;
T2 second;

come in this order, so first will be initialized before second. This means that in your particular case, the string will be copied before it is moved away, and you're safe.

However, if you were doing it the other way around:

m.emplace(std::move(A.s), A); // huh?

...then you'd get funny effects.

like image 114
Wintermute Avatar answered Nov 24 '22 00:11

Wintermute