2

Is it safe to operate on object within arguments' list, when there is also std::move() invoked on that object ?

void foo(int* raw, std::unique_ptr<int> u)
{
    *raw = 456;
}

std::unique_ptr<int> p(new int(123));
foo(p.get(), std::move(p));

Will the `raw' pointer in foo() be valid if std::move(p) was evaluated as the first parameter ?

Marc Andreson
  • 3,405
  • 5
  • 35
  • 51
  • sure, but will "p.get()" return the pointer to my value=123, if std::move(p) was evaluated first ? – Marc Andreson Aug 07 '14 at 10:43
  • 5
    Considering that the evaluation order of arguments is not specified and can happen in any order, then no this isn't safe. If the `std::move` operation was evaluated first, then `p.get()` will return `nullptr`. – Some programmer dude Aug 07 '14 at 10:44
  • 1
    [Pretty easy to check for yourself](http://ideone.com/smXG2K). – T.C. Aug 07 '14 at 10:47
  • 5
    `std::move` does nothing, so that cannot possibly matter. It's `std::unique_ptr`'s constructor, implicitly called, that's destructive. –  Aug 07 '14 at 10:58
  • @hvd: but the code seems to crash the program when executed, I think when second argument is evaluated then it also implicitly invokes the unique_ptr<> ctor of parameter, which steals the pointer from `p', doesn't it ? – Marc Andreson Aug 07 '14 at 11:02
  • 4
    @MarcAndreson Exactly. (I did try to cover that in my comment.) I wanted to point it out because if your function had a `std::unique_ptr &&` parameter, there would be absolutely no issue, even though the call would look exactly the same, including the `std::move` part. –  Aug 07 '14 at 11:08
  • great, then if I had an std::map>, and I used myMap.emplace(p.get(), std::move(p)) then it should be totally safe, because map::emplace gets r-value references? (actually this is my original concern, can I std::move arguments to emplace method) – Marc Andreson Aug 07 '14 at 11:13
  • @MarcAndreson As far as I can tell, that should be okay, and does not have the problem that the code in your question does have. –  Aug 07 '14 at 11:18
  • please see edit; iavr points out my fault. – ikh Aug 07 '14 at 11:19
  • 1
    Related to [move-semantics-and-function-order-evaluation](http://stackoverflow.com/questions/24814696/move-semantics-and-function-order-evaluation) – Jarod42 Aug 07 '14 at 12:14

3 Answers3

3

No, it's NOT safe. the eval order of argument is not specified in standard. So your code can be run as:

  1. std::move(p).
  2. call move constructor of std::unique_ptr<int>.
  3. p.get() (because of 2., this will be nullptr.) and pass this parameter.
  4. call foo.

You have to do like this:

int *raw = p.get();
foo(raw, std::move(p));

Notice that your code can work well, because some compilers can compile your code into 3 -> 1 -> 2 -> 4. However, it doesn't mean code is safe. it's not specified standard >o<

ikh
  • 10,119
  • 1
  • 31
  • 70
  • 5
    But `std::move` doesn't do anything. It's just a type cast. – iavr Aug 07 '14 at 11:08
  • 1
    @iavr, true, but the `std::unique_ptr u` is created in this mix, and it's part of the unspecified order (so it can happen at any time (between `1.` and `3.` (update `4.`)) before `foo` is called). – Niall Aug 07 '14 at 11:16
  • so if foo() would simply std::forward() arguments to other function instead of operating on them, then it would be safe, because before arguments get forwarded, they are evaluated, right ? – Marc Andreson Aug 07 '14 at 11:20
  • 1
    @MarcAndreson Yes, **if the type of the parameter of `foo` is rvalue reference, which is `std::unique_ptr &&**. But I don't suggest it - the easier code is, the better. – ikh Aug 07 '14 at 11:57
  • @ikh: it's not hard to find an example of such code: std::map::emplace(T&&...) gets r-value references, I wonder what would happen if emplace was inlined by the compiler? will the arguments still be evaluated before entering this inline function? because std::forward is an inline function, so is there still a threat that std::map::emplace(k, std::move(k)) is not safe ? – Marc Andreson Aug 07 '14 at 12:00
  • @MarcAndreson That's no regard, because the parameters of `std::forward` is also rvalue reference >o – ikh Aug 07 '14 at 12:05
  • not sure if I understood you correctly, but if std::map::emplace AND std::forward AND std::pair constructor were ALL inlined by compiler, could this somehow skip the evaluation of parameters before operating on them (i.e. stealing pointer to build map's value-pair prior to evaluating first argument of emplace) ? – Marc Andreson Aug 07 '14 at 12:09
  • I meant that "foo(p, std::move(p))" results in evaluating parameters (in any order) before entering the function's body, but does the same apply to inline functions? because if not, then even when my function accepted r-value reference (unique_ptr&&) as parameter instead of unique_ptr, I can all in all end up with my original problem when the content is stolen before other parameter's value is calculated, e.g. the unique_ptr inserted to map> before "int*" was evaluated to be the key of my map in myMap.emplace(p.get(), std::move(p)) call. – Marc Andreson Aug 07 '14 at 12:19
  • 1
    @MarcAndreson Whether it's inline or not, arguments is evaluated before calling. (It seems I haven't got your point yet, because of my poor understanding or poor English skill >o<) – ikh Aug 07 '14 at 12:36
  • "Whether it's inline or not, arguments is evaluated before calling" - that fully solves my concern, thanks. – Marc Andreson Aug 07 '14 at 12:38
2

Here are answers about argument evaluation order - In short: the order is not specified in standard and may be different per platform, compiler and calling convention.

But I wanted to test it so here are results for Cygwin GCC:

#include <iostream>
#include <memory>
using namespace std;
void print(int* p) {
    cout << (p == nullptr ? "null" : "valid") << endl; }
void foo(int* p, unique_ptr<int> u) {
    print(p); }
void bar(unique_ptr<int> u, int* p) {
    print(p); }
__stdcall void foo2(int* p, unique_ptr<int> u) {
    print(p); }
__stdcall void bar2(unique_ptr<int> u, int* p) {
    print(p); }
__cdecl void foo3(int* p, unique_ptr<int> u) {
    print(p); }
__cdecl void bar3(unique_ptr<int> u, int* p) {
    print(p); }
int main() {
    unique_ptr<int> p(new int(1)), q(new int(2));
    foo(p.get(), move(p)); bar(move(q), q.get());
    unique_ptr<int> p2(new int(1)), q2(new int(2));
    foo2(p2.get(), move(p2)); bar2(move(q2), q2.get());
    unique_ptr<int> p3(new int(1)), q3(new int(2));
    foo3(p3.get(), move(p3)); bar3(move(q3), q3.get());
}

Output:

null
valid
null
valid
null
valid

Surprise is that I could not force it to change the order even when I used __stdcall and __cdecl.

EDIT: Same test with MSVC 2012 (__stdcall/__cdecl moved before names), same result!

Community
  • 1
  • 1
firda
  • 3,268
  • 17
  • 30
0

Calling std::move() on arguments is perfectly safe.

What is not safe is tripping yourself up by writing to a raw pointer that was managed by an object that has relinquished the pointer's memory to the free store.

Galik
  • 47,303
  • 4
  • 80
  • 117
  • this is just an example to illustrate the problem – Marc Andreson Aug 07 '14 at 11:43
  • 2
    @Marc What I mean is there is nothing inherently unsafe about using `std::move()`. What your example is doing is undefined behaviour. Not because you used `std::move()` but because your example relies on the order of execution of two functions whose order of execution is undefined. – Galik Aug 07 '14 at 12:07