3

About std::move, here is what I can interpret, according to http://en.cppreference.com/w/cpp/utility/move :-

  • If I want to transfer ownership, I have to call std::move (or in rare case, std::forward).
  • Responsibility of std::move is calling operator=(A&& other).
  • The most essential step of the move operation is supposed to be implemented in operator=(A&&).
  • It is tricky to ensure that operator=(A&&) would be called. It need a special converter.
  • There are only two converters in the C++ world that can convert variables into xvalue (the &&) : std::move and std::forward.

Question

After adding many of std::move(std::unique_ptr) in my code, I start to worry that for such basic feature like transfer ownership, I have to heavily rely on the standard library (std::).

Do I really have to use std::move to transfer ownership?

Is spamming and hard-code calling std::move in many places of code-base a correct way to go for a high-standard program?

Should std::move be encapsulated?

They are actually a single question, but ask in different perspectives.

Edit

As request, here is my trial & error. It compiled ok.
I have no problem about the code, but I worry about its approach / pattern. https://ideone.com/y8Pcgf

class T{
    public: int value;
    public: T(int a=1234){
        value = a;
    }
};
int main() {
    std::unique_ptr<T> t1 = std::unique_ptr<T>(new T(1));
    void* databaseNew=operator new [](sizeof(std::unique_ptr<T>));
    std::unique_ptr<T>* t1ptr=static_cast<std::unique_ptr<T>*>(databaseNew);
    new (t1ptr) std::unique_ptr<T>(std::move(t1));
    return 0;
}
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 4
    1. no, 2. no, 3. yes (and/or move-constructor), 4. no, 5. no – M.M Sep 19 '16 at 05:52
  • 5
    If you want or need to *explicitly* transfer ownership of an object, then yes you need to use `std::move`. Otherwise let the compiler handle it. Also, what `std::move` actually does is not very much, it is basically a cast of the object to an rvalue reference. – Some programmer dude Sep 19 '16 at 05:53
  • 2
    It is nigh impossible to answer your question as posted. It sounds like you don't understand move semantics and value categories, and are programming by trial and error. A better idea would be to post an actual [piece of source code](http://stackoverflow.com/help/mcve) that you feel has problems with the use of `std::move` and then we can comment on what you are doing right and wrong. – M.M Sep 19 '16 at 05:53
  • @M.M You are correct, I just edited my question to add my trial & error. – javaLover Sep 19 '16 at 05:55
  • 3
    You might also want to [read more about move semantics and what it is](http://stackoverflow.com/q/3106110/440558). The two top answers to that question are very detailed and good. – Some programmer dude Sep 19 '16 at 05:55
  • 1
    It's not possible to use "placement operator new[]" like that. [see here](http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the-buffer) for detail. But supposing you change that to non-array placement new. Then you only have `t1`, and you `std::move` from `t1` into another object. That's fine. But you could have omitted `t1` and constructed the placement-new object with the `new T(1)`. – M.M Sep 19 '16 at 05:59
  • @M.M What? Are you sure? With a small modification, I just test it and my program can run correctly without leak. Hmm.... I may be wrong... – javaLover Sep 19 '16 at 06:02
  • 1
    by "not possible", I mean that there could be buffer overflow introduced by the implementation and you have no reliable way of knowing or working around it, see Howard's answer for detail – M.M Sep 19 '16 at 06:03
  • This is a common misconception. `std::move` just shows the programmer's intent to move the callee (by casting it to `&&`), it doesn't do any actual work. – Rerito Sep 19 '16 at 07:36

2 Answers2

4

Rule of thumb:

If you're in a deduced x-value context, use std::forward:

template<class T>
void foo(T&& t)       // T is deduced x-value, so we forward it
{
  bar(std::forward<T>(t));
}

Otherwise use std::move

template<class T>
void foo1(std::vector<T> v)   // although vector<T> is deduced, it's not an x-value
{
    bar(std::move(v));  // so move it
}

template<class T>
void foo2(std::vector<T>&& v)   // although vector<T> is deduced, it's not an x-value. 
                                // In this case an r-value reference
{
    bar(std::move(v));  // so move it
}

template<class T>
void foo3(std::vector<T>& v)   // although vector<T> is deduced, it's not an x-value. 
                               // In this case an l-value reference
{
    bar(std::move(v));  // so move it
}

void foo4(std::vector<int> v)   // complete type
{
    bar(std::move(v));  // so move it
}

void foo5(std::vector<int> const & v)   // const reference
{
    bar(v);  // not much point in moving it. std::move would cast it
             // to std::vector<int> const&&, which although is detectable
             // decays to std::vector<int> const&
}

which although is detectable... what?

It is permissible, if not necessarily advisable to write code like this:

#include <iostream>

struct X
{
  void foo() const &
  {
    // do one thing...
    std::cout << "one thing\n";
  }

  void foo() const &&
  {
    // do something else...
    std::cout << "or another\n";
  }
};

int main()
{
  const X x;
  x.foo();
  std::move(x).foo();
}

const r-value references do exist, it's just that no-one uses them because there is no reasonable use-case.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 3
    I am pretty sure people who ask when to use `std::move` don't know when they are in a deduced x-value context. – nwp Sep 19 '16 at 09:58
  • 1
    @nwp you are of course right. I am hoping the non-x-value examples will offer some insight. – Richard Hodges Sep 19 '16 at 10:06
2

The need to explicitly move, of which you complain, was actually done on purpose. Before unique_ptr, STL had a horrid construct called auto_ptr. It would move ownership impllicitly, and was borderline unusable unless you really really really knew what you were doing.

To make things more usable, in most cases C++ now requires you to explicitly state that you intend on moving ownership over a container, by using std::move.

In fact, std::move is little more than a cast to an rvalue reference.

There are cases where such an explicit specification is not necessary. For example, if the container from which you take ownership is already an rvalue (e.g. - a temporary object), then no case using std::move is necessary. For example, the following doesn't compile:

std::unique_ptr<int> a;
a = new int;

But the following does, without needing a move:

std::unique_ptr<int> a;
a = std::unique_ptr<int>(new int);

The reason this does not need a call to std::move, despite invoking the move operator, is that the object we move the ownership away from is already a temporary object (i.e. - an rvalue), so no cast is necessary.

Another example is if you call a function that returns a unique_ptr. You might have to call std::move inside the function to get it into the return value, but you do not need to call std::move on the function's return value to get it into the outside unique_ptr. It is already an rvalue, and therefor no cast is necessary.

Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57