That's the trick data().swap(..) does, it takes the passed by value data and swaps its pointer with the object calling it <--I can discuss a bit more about that but it may get too technical (i.e. std::swap(..,..) vs container.swap(...) ). Also the compiler should be (in my case is) responsible to take care of the value passed using copy elision. If it is an rvalue it will pass it in without copying; or copy it if it is not - which is what we want.
I did some tests and if the you are interested I can prepare to post them. The memory in those tests certainly shows half memory usage with move semantics for a large vector. Tracing back gives no indication of the existence of two containers and the performance is increased by about 20-30%. Also there is no indication of shallow copy (left and right side containers remain two distinct objects with distinct storage).