Thread: Trouble understanding some code related to std::move

  1. #1
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    308

    Trouble understanding some code related to std::move

    What's the difference between:

    1)
    Code:
    inline Foo operator + (const Foo& L, const Foo& R) {
        return Foo(L) += R;
    }
    2)
    Code:
    inline Foo operator + (const Foo& L, const Foo& R) {
        return std::move(Foo(L) += R);
    }
    3)
    Code:
    inline Foo operator + (const Foo& L, const Foo& R) {
        return std::move(Foo(std::move(L)) += R);
    }
    3)
    Code:
    inline Foo operator + (const Foo& L, const Foo& R) {
        return std::move(Foo(std::move(L)) += std::move(R));
    }
    Q) Doesn't the (1)'st version elide the copy (return value optimisation)? (Hence, copy constructor is called only once, right?)
    Q) If so, why is it that (2) is faster than (1)? (Copy constructor is called only once, right?)
    Q) Is (3) a correct thing to do?
    Q) Is (4) a correct thing to do?
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,412
    Quote Originally Posted by Zeus_
    Q) Doesn't the (1)'st version elide the copy (return value optimisation)? (Hence, copy constructor is called only once, right?)
    I think the possible issue is that the reference returned from operator+= prevents RVO (I'm thinking that the compiler has to do additional static analysis to determine that the reference does refer to the copy, and it might decide that is too much work), and you end up with a second copy constructor call instead of a move constructor call for the same reason (compiler cannot easily tell from the reference that it refers to what is effectively a local "temporary", and it isn't an rvalue reference). You could try with the expanded version to see if you do get RVO once the reference is not involved, e.g.,
    Code:
    inline Foo operator + (const Foo& L, const Foo& R) {
        Foo temp{L};
        temp += R;
        return temp;
    }
    I think it was for this reason that some C++ programmers in pre-C++11 days advocated having the first parameter be Foo instead, hence omitting the need for temp, but revealing a bit about the implementation of the operator in its interface. Personally, I think sticking to const Foo& is better.

    There's also the issue whereby if at the call site you're not constructing an object, RVO cannot be used anyway, but in such a case we should expect move assignment so that shouldn't be the problem.

    Quote Originally Posted by Zeus_
    Q) If so, why is it that (2) is faster than (1)? (Copy constructor is called only once, right?)
    What you're doing here is telling the compiler to move construct the return value from the reference (converted to an rvalue reference by std::move) returned by operator+=, so if copy construction is expensive compared to move construction, you have a win. But if you can have RVO or directly involve move assignment instead, that's even better.

    Quote Originally Posted by Zeus_
    Q) Is (3) a correct thing to do?
    No. You're saying "let's move from L" to move construct a temporary, but L could refer to an object that is not a temporary and is expected to be unchanged, and doing this could then lead to a bug where L is used as-is but it has been moved from, possibly changing its state. I think that if you want to do this, the right approach is to overload operator+ with a Foo&& parameter, hence ensuring that the move constructor is only invoked if L really is a temporary or if it has deliberately had std::move applied so the caller is responsible for handling it in its moved from state. So, we might be looking at something like this:
    Code:
    inline Foo operator + (Foo&& L, const Foo& R) {
        Foo temp{std::move(L)};
        temp += R;
        return temp;
    }
    hence getting a more efficient implementation when the left hand operand is a temporary, while keeping a correct implementation otherwise. Checking the C++ Core Guidelines, this falls under F.18: For “will-move-from” parameters, pass by X&& and std::move the parameter.

    Quote Originally Posted by Zeus_
    Q) Is (4) a correct thing to do?
    Same issue as (3), except that the std::move(R) is pointless since the right hand parameter of operator+= is a const reference.
    Last edited by laserlight; 04-20-2020 at 06:07 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Aug 2019
    Location
    inside a singularity
    Posts
    308
    I think the possible issue is that the reference returned from operator+= prevents RVO (I'm thinking that the compiler has to do additional static analysis to determine that the reference does refer to the copy, and it might decide that is too much work), and you end up with a second copy constructor call instead of a move constructor call for the same reason (compiler cannot easily tell from the reference that it refers to what is effectively a local "temporary", and it isn't an rvalue reference). You could try with the expanded version to see if you do get RVO once the reference is not involved, e.g.,
    Yes, indeed. Just did some tests and I found that RVO kicks back in when used as in the example you provided. The reference return from += is what causes the confusion and hence no RVO. I knew that (3) and (4) are not entirely bug-free but what made me ask was that I saw it in someone else's code (on GitHub) and it made me wonder why would they do something that way (because L and R become useless after they're moved-from). It is now that I noticed that the last comment at the bottom says "Move Semantics tests".

    Thanks for the help!
    "Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook, The Wizardry Compiled

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. I have trouble understanding this code
    By amirghannadan in forum C Programming
    Replies: 2
    Last Post: 02-29-2016, 08:07 PM
  2. Trouble understanding some code please help
    By Shinzu911 in forum C Programming
    Replies: 1
    Last Post: 02-19-2013, 09:01 PM
  3. Trouble understanding the concepts behind the code
    By curlygeek in forum C Programming
    Replies: 10
    Last Post: 10-14-2012, 04:31 AM
  4. Trouble understanding code to calculate sum of n numbers
    By jackloring in forum C Programming
    Replies: 8
    Last Post: 11-13-2010, 04:33 AM
  5. trouble understanding collision detection code
    By silk.odyssey in forum Game Programming
    Replies: 5
    Last Post: 06-16-2004, 02:27 PM

Tags for this Thread