1

Wrt. the suggested duplicate "Is pass-by-value a reasonable default in C++11?" - neither the question there nor the answers there make any mention of the "universal-reference" constructor version, so I really fail to see the duplication. Consider reopening.


I am getting familiar with the move semantic, experimenting with it. Please take a look at this (compilable) piece of code:

#include <iostream>
#include <string>

struct my_str {
  std::string s;

  my_str(const std::string & str): s(str) { std::cout << "  my_str parameter ctor" << std::endl; }
  my_str(const my_str & o): s(o.s)        { std::cout << "  my_str copy ctor" << std::endl; }
  my_str(my_str && o): s(std::move(o.s))  { std::cout << "  my_str move ctor" << std::endl; }
};

template <typename T>
my_str build_ur(T && s) {
  return my_str(std::forward<T>(s));
}

my_str build_val(my_str s) {
  return my_str(std::move(s)); 
}

int main() {
  my_str s1("hello");
  my_str s2("world");

  std::cout << "Building from universal reference (copy):" << std::endl;
  build_ur(s1);
  std::cout << "Building from universal reference (move):" << std::endl;
  build_ur(std::move(s1));

  std::cout << "Building from value (copy):" << std::endl;
  build_val(s2);               
  std::cout << "Building from value (move):" << std::endl;
  build_val(std::move(s2));    

  std::cout << std::endl;
  return 0;
}

Output:

g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
  my_str parameter ctor
  my_str parameter ctor
Building from universal reference (copy):
  my_str copy ctor
Building from universal reference (move):
  my_str move ctor
Building from value (copy):
  my_str copy ctor
  my_str move ctor
Building from value (move):
  my_str move ctor
  my_str move ctor

http://coliru.stacked-crooked.com/a/3be77626b7ca6f2c

Both the functions do the right job in both cases. The by-value function calls the move constructor once more, which however should be cheap. Could you comment on situations in which one pattern should be preferred to the other?

Petr
  • 62,528
  • 13
  • 153
  • 317
DarioP
  • 5,377
  • 1
  • 33
  • 52
  • 1
    I'd go with my_str build(T && s), since my_str build(my_str s) will move the local-function variable s (that will be copy constructed when passed to the function) – Exceptyon May 22 '14 at 09:28
  • What compiler are you using? The first version should definitely use a copy ctor. https://ideone.com/L8ZrI5 – Danvil May 22 '14 at 10:17
  • @Dario: mmm are you using some optimization? I actually see a ctor with my gcc 4.8.1 – Exceptyon May 22 '14 at 10:20
  • @Exceptyon I update the code, now my intent should be clearer. – DarioP May 22 '14 at 10:40
  • 1
    @Jarod42: You are wrong. A moved-from object is in an ["unspecified but valid state"](http://stackoverflow.com/questions/7027523/what-can-i-do-with-a-moved-from-object). It's not UB. – Martin Ba May 22 '14 at 11:05
  • Hmmm ... An alternative title for this question may be "pass-by-value or pass-by-universal-reference for sink arguments?" (which should make [this](http://stackoverflow.com/questions/7592630/is-pass-by-value-a-reasonable-default-in-c11) slightly related – Martin Ba May 22 '14 at 11:16
  • And this one may help too(?): http://stackoverflow.com/questions/18673658/should-i-always-move-on-sink-constructor-or-setter-arguments – Martin Ba May 22 '14 at 11:17
  • @MartinBa thanks for the correct wordings (and for the links). I hope now the question is very clear. – DarioP May 22 '14 at 11:31
  • @MartinBa They seemed to both ask for the differences between the two? Only this question used the word "better" in stead of "difference". But I suppose this one's more specific. – Aberrant May 22 '14 at 12:29
  • I believe this question should be re-opened. The supposed duplicate doesn't discuss universal references at all. – Petr Nov 29 '21 at 15:55

1 Answers1

0

Your two version of build do different things.

Version 1: "Create a copy"

my_str build_plain(my_str s) {
  return my_str(std::move(s)); 
}

Creates a copy of the argument using the copy constructor. Then moves this copy into the result object. The object given to the function will be valid afterwards. This function does not make that much sense as my_str build(my_str s) { return s; } would achieve the same thing.

Version 2: "Move it"

template <typename T>
my_str build_templated(T && s) {
  return my_str(std::forward<T>(s));
}

Moves the argument object into a new object and return that new object. The object given to the function will be invalid afterwards! This is the classic case of moving an object.


To summarize: Version 1 is (almost) equivalent to my_str t = s;, while Version 2 is (almost) equivalent to my_str t = std::move(s);.

Danvil
  • 22,240
  • 19
  • 65
  • 88
  • both clang and gcc do not call any copy ctor even with the first function when it is called the second time in the main.. Please, have a better check! – DarioP May 22 '14 at 10:26
  • Look here: https://ideone.com/L8ZrI5 or even in the link you gave: http://coliru.stacked-crooked.com/a/5c2f3e3eefd4bc7c. Both print `my_str copy ctor`. – Danvil May 22 '14 at 10:26
  • Yes, in the first call where I want to copy it. In the second, where a move is desired, no copy constructors are called in both cases.. – DarioP May 22 '14 at 10:29
  • Did I state otherwise? What do you mean with "both cases"? There is only one case in your code which uses the second version: `build(std::move(s));` – Danvil May 22 '14 at 10:30
  • I updated the code, now it should be clearer. Please forgive me if the initial version caused confusion. – DarioP May 22 '14 at 10:40
  • Hmm not sure how to interpret this answer. Taking by value doesn't imply a copy; for rvalues this can be a move, and that move can be elided. Similarly, the second one can copy for lvalue arguments. Version 2 invokes one less constructor call in some scenarios. – dyp May 22 '14 at 11:37
  • @dyp: OP changed the question two times since/while I answered... – Danvil May 22 '14 at 11:39
  • I admit that the initial version was not clear (did I say that I'm getting familiar with this? - Yes I did!), but I didn't change what I wanted to ask. Feel free to delete your answer or adapt it. However I think that saying that `return my_str(std::move(s));` is the same of `return my_str(s);` is quite a big mistake. – DarioP May 22 '14 at 12:07
  • @DarioP No, not really. The compiler will treat `s` as an rvalue in the return statement of `my_str build(my_str s) { return s; }`. Due to move elision, that's very much like `my_str build(my_str s) { return my_str(std::move(s)); }` (since the creation of that temporary will be elided). – dyp May 22 '14 at 12:17
  • @dyp oh yes, I misred `return s;` as `return my_str(s);`. However that is another interesting point that was not so clear, so thanks! – DarioP May 22 '14 at 12:25