-1

I would like to doublecheck my understanding of move semantics. Am I missing anything in my reasoning:

#include <iostream>

using std::cout;

struct A
{
    A() {cout<<"Constructor\n";}
    ~A() {cout<<"Desctuctor\n";}
    A(const A&) {cout<<"Copy Constructor\n";}
    A(A&&) noexcept {cout<<"Move Constructor\n";}
    int x{1}; 
};

int f1(A a)
{
    cout<<"f1(A a)\n";
    return a.x;
}

int f2 (A&& a)
{
    cout<<"f2 (A&& a)\n";
    return a.x;
}

int main()
{
  A a1, a2;
  f1(std::move(a1));
  f2(std::move(a2));
}

Output:

Constructor
Constructor
Move Constructor
f1(A a)
Desctuctor
f2 (A&& a)
Desctuctor
Desctuctor

From what I can see with f2(), I'm not creating any additional copies, even "light" move copies, which is cool. So my understanding now is that if I'm going to use move semantics, I better always write functions/methods signature to accept r-values as to avoid any unnecessary copies at all. Or there is something else I'm missing ?

DoehJohn
  • 201
  • 1
  • 8
  • You're missing having one that takes its parameter by value. Add then then think about the results again. – Taekahn Jun 02 '20 at 19:13
  • Trying to optimize without taking your actual code, with an optimized build, and actually profiling it is a fool's errand. The fact that you have a print statement in your constructors and destructors fundamentally changes how the generated binary will be built because now they have observable behavior. You fundamentally don't understand how to optimize code and you're going down the wrong path to learn. – xaxxon Jun 02 '20 at 19:13
  • 1
    Your `f1()` and `f2()` are not procedural functions (non-void), either define them as `void` functions, or with a `return` value, it's recommended also for the `main()` while you put an `int` as a data type return, so, your code is firstly not safe implemented. – rekkalmd Jun 02 '20 at 19:20
  • ironically you are missing some `#include` s – M.M Jun 02 '20 at 20:54

1 Answers1

2

You're almost always best just taking inputs you're going to keep by value (except for odd types which have very high costs of moves).

It maintains the same level of performance (in common scenarios) and keeps your code very simple to understand. It also makes it very clear that your code is keeping the value (because it's guaranteed to get moved out of), as an rvalue reference doesn't enforce that.

As you can see, the timings and generated code are identical for your examples when the print statements are removed: http://quick-bench.com/ADU4fzd0ISk0UrLboVhC-Pd7RmI

xaxxon
  • 19,189
  • 5
  • 50
  • 80
  • Wow, despite one is calling to move constructor and other's not assembler codes are identical, how it's possible? – DoehJohn Jun 03 '20 at 12:12
  • @DoehJohn it's not calling the move constructor - that's why. https://en.cppreference.com/w/cpp/language/copy_elision https://www.verywellmind.com/an-overview-of-the-dunning-kruger-effect-4160740#:~:text=The%20Dunning%2DKruger%20effect%20is,to%20recognize%20their%20own%20incompetence. – xaxxon Jun 03 '20 at 14:53