28

If I have a constructor with n parameters such that any argument to that can be an rvalue and lvalue. Is it possible to do support this with move semantics for the rvalues without writing 2^n constructors for each possible rvalue/lvalue combination?

Luc Danton
  • 33,152
  • 5
  • 66
  • 110
Opt
  • 4,628
  • 3
  • 24
  • 28
  • 1
    Give the code example to make it more clear. (who knows, may be you don't need `move` constructor at all!) – iammilind Jul 14 '11 at 04:50
  • 2
    Can you show your current code? As far as I know, the move constructor is the "special" constructor `Foo(Foo&& that)`, so I'm a little confused about what you're talking about. – zneak Jul 14 '11 at 04:52
  • @zneak: move constructor will take references to the same class only, i think Sid is talking about passing any 'n' parameters to the constructor. @Sid: if you try to pass each parameter into separate functions(not the constructor), then you will have a worst case of 2*n functions, one for rvalue and one for lvalue for each argument. – A. K. Jul 14 '11 at 05:05
  • 1
    @zneak, @Aditya: Yeah, I was talking about passing any 'n' parameters to the constructor. I was in a hurry so I entitled the question terribly. I'll fix that. – Opt Jul 14 '11 at 08:36

3 Answers3

36

You take each one by value, like this:

struct foo
{
    foo(std::string s, bar b, qux q) :
    mS(std::move(s)),
    mB(std::move(b)),
    mQ(std::move(q))
    {}

    std::string mS;
    bar mB;
    qux mQ;
};

The initialization of the function parameters by the argument will either be a copy-constructor or move-constructor. From there, you just move the function parameter values into your member variables.

Remember: copy- and move-semantics are a service provided by the class, not by you. In C++0x, you no longer need to worry about how to get your own "copy" of the data; just ask for it and let the class do it:

foo f("temporary string is never copied", bar(), quz()); // no copies, only moves
foo ff(f.mS, f.mB, f.mQ); // copies needed, will copy
foo fff("another temp", f.mB, f.mQ); // move string, copy others

Note: your constructor only takes in values, those values will figure out how to construct themselves. From there, of course, it's up to you to move them where you want them.

This applies everywhere. Have a function that needs a copy? Make it in the parameter list:

void mutates_copy(std::string s)
{
    s[0] = 'A'; // modify copy
}

mutates_copy("no copies, only moves!");

std::string myValue = "don't modify me";
mutates_copy(myValue); // makes copy as needed
mutates_copy(std::move(myValue)); // move it, i'm done with it

In C++03, you could emulate it fairly well, but it wasn't common (in my experience):

struct foo
{
    foo(std::string s, bar b, qux q)
    // have to pay for default construction
    {
        using std::swap; // swaps should be cheap in any sane program

        swap(s, mS); // this is effectively what
        swap(b, mB); // move-constructors do now,
        swap(q, mQ); // so a reasonable emulation
    }

    std::string mS;
    bar mB;
    qux mQ;
};
GManNickG
  • 459,504
  • 50
  • 465
  • 534
  • 3
    +1 Great, in the new world, you just take your arguments by value and everything will be as good as it can be. That's wonderful. Could you add the assignment operator as well for fun? – Kerrek SB Jul 14 '11 at 11:06
  • 2
    @Kerrek: You take values if you need copies of the arguments, but not if you only need to inspect them (const reference) or modify them (reference). I'm sure you probably know this, but someone reading your comment might not. – Benjamin Lindley Jul 14 '11 at 16:16
  • `mutates_copy("no copies, only moves!")` does copy string literal in `std::string` ctor, there is no way to move a string literal to an `std::string`. – Gene Bushuyev Jul 15 '11 at 07:07
  • `foo(std::string s, bar b, qux q) : mS(s)...` works even better, since compiler can elide copy ctor, when a source is an rvalue, which is better than move. – Gene Bushuyev Jul 15 '11 at 07:12
  • the last c++03 `struct foo` is a bad example, because you default construct and then swap instead of using initialization and allowing compiler to elide the copies. – Gene Bushuyev Jul 15 '11 at 07:18
  • @Gene: #1 I didn't intend to say it moves the literal, only the temporary it creates. #2 I don't understand. `s`, `b`, and `q` will never be rvalues, in the constructor. You have to explicitly move them to make them rvalues. Your version will *always* make a copy, into the member. #3 Again, once inside the constructor no move elisons are possible because we only have lvalues. Elison doesn't apply. – GManNickG Jul 15 '11 at 17:46
  • @GMan: it will not create copies, *although the compiler is normally required to make a copy when a function parameter is passed by value (so modifications to the parameter inside the function can’t affect the caller), it is allowed to elide the copy, and simply use the source object itself, when the source is an rvalue* -- http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ – Gene Bushuyev Jul 15 '11 at 22:55
  • @Gene: I know what elison is. What point are you replying to? – GManNickG Jul 16 '11 at 19:34
  • @GMan: explicitly moving you prevent copy elision, so it's unnecessary and might be slower, just let the compiler do the optimization. – Gene Bushuyev Jul 17 '11 at 00:09
  • 3
    @Gene: But, you're not dealing with rvalues. You may have had rvalues passed into the foo constructor. Those would then be moved into the lvalues s, b and q. In your version, you use s directly to construct mS. s is *not* an rvalue, so neither elision nor move construction happens. You can demonstrate it with noisy constructors. http://ideone.com/CRbaA – Benjamin Lindley Jul 17 '11 at 00:48
  • @Gene: Like Ben says and I said earlier, *elison does not apply here*. There are no rvalues within the constructor. `s`, `b` and `q` are lvalues, and the copy to the member is always a regular copy, except if you explicitly move it (which we can now gladly do). – GManNickG Jul 17 '11 at 03:39
  • @GMan: I just looked at the optimized assembly and when there is an explicit move `mS(move(s))`, compiler (VC2010) created a copy (expected) and initialized the member using move ctor. In case of no explicit move `ms(s)`, compiler just initialized a member using copy ctor from source. So all copies were elided. – Gene Bushuyev Jul 17 '11 at 07:14
  • @Gene: How can you say "initialized a member using copy ctor" then say "*all* copies were elided"? That's a contradiction. – GManNickG Jul 17 '11 at 08:04
  • @GMan: no contradiction, the assembly I looked at was for lvalue parameter. – Gene Bushuyev Jul 17 '11 at 21:17
  • @Benjamin Lindley, remove the side effects and see how compiler removes all the extra copy ctor. – Gene Bushuyev Jul 17 '11 at 21:18
  • @Gene: Please, read what you said again. If **all** copies are elided, there are **zero** copies. That implies that no members are initialized using the copy constructor. Yet you said: "compiler just initialized a member using copy ctor from source" which implies there *is* a copy. Which is it? No copies or some copies? Also, Ben doesn't have to change a thing: compilers ignore side-effects when it comes to this optimization, as allowed by the standard. Like we've been saying, do you understand what lvalues and rvalues are? Do you understand elison **only** happens with rvalues? – GManNickG Jul 17 '11 at 21:47
  • @Gene: Okay, I removed the side effects. My compiler now generates no assembly code for either case. This doesn't tell me anything. – Benjamin Lindley Jul 17 '11 at 22:10
  • @Benjamin Lindley & GMan: fair enough, my testing shows that compiler indeed isn't smart enough to optimize away lvalue copies every time "as-if" rule allows it, though it does optimization in simple cases. – Gene Bushuyev Jul 18 '11 at 16:30
  • @Gene: The compiler isn't allowed to optimize away lvalue copies, only rvalue. That's why it's better to move explicitly, because moves are cheaper. – GManNickG Jul 18 '11 at 18:19
  • @GMan: compiler can and does optimize lvalue copies following as-if rule – Gene Bushuyev Jul 18 '11 at 18:36
  • @GMan: one more thing, move isn't always cheap. The same string due to short string optimization would cause copy of short strings on move, so compiler optimization, which eliminates copies altogether is still better. – Gene Bushuyev Jul 18 '11 at 18:39
  • @Gene: No, only rvalues. I've written a commented explanation here: https://ideone.com/KXNIa. And yes, I should have said it's often cheaper than a copy but never less. And I don't why you then follow that with a comparative statement, when we've established there's nothing to compare to: **the compiler cannot help you here**. End of story. It's objectively better to move the function arguments to the members then do nothing. (Yes: do nothing. Again, compiler does not assist here in any way.) – GManNickG Jul 18 '11 at 18:57
  • @GMan: yes, copy elision can happen regardless the side effects, but other compiler optimization follow as-if rule: *an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program.* – Gene Bushuyev Jul 18 '11 at 19:12
  • @Gene: Like I said in the code, I have nothing more to say, you're just simply wrong. You can also stop quoting things to me, I know what they are. Do you understand that copy elison *breaks* the as-if rule, in principle? Saying that compilers follow the as-if rule actually only harms your argument for them doing copy elison. – GManNickG Jul 18 '11 at 19:16
2

Take the following code ideone link.

#include <iostream>

class A
{
public:
  A() : i(0) {}
  A(const A& a) : i(a.i) { std::cout << "Copy A" << std::endl; }
  A(A&& a) : i(a.i) { std::cout << "Move A" << std::endl; }
  int i;
};

template <class T>
class B1
{
public:
  template <class T1, class T2>
  B1(T1&& x1_, T2&& x2_) : x1(std::forward<T1>(x1_)), x2(std::forward<T2>(x2_)) {}
  B1(const B1<T>& x) : x1(x.x1), x2(x.x2) { std::cout << "Copy B1" << std::endl; }
  B1(B1<T>&& x) : x1(std::move(x.x1)), x2(std::move(x.x2)) { std::cout << "Move B1" << std::endl; }
private:
  T x1;
  T x2;
};

template <class T>
class B2
{
public:
  B2(T x1_, T x2_) : x1(std::move(x1_)), x2(std::move(x2_)) {}
  B2(const B2<T>& x) : x1(x.x1), x2(x.x2) { std::cout << "Copy B2" << std::endl; }
  B2(B2<T>&& x) : x1(std::move(x.x1)), x2(std::move(x.x2)) { std::cout << "Move B2" << std::endl; }
private:
  T x1;
  T x2;
};

A&& inc_a(A&& a) { ++a.i; return static_cast<A&&>(a); }
A inc_a(const A& a) { A a1 = a; ++a1.i; return a1; }

int main()
{
  A a1;
  A a2;
  std::cout << "1" << std::endl;
  B1<A> b1(a1,a2);
  std::cout << "2" << std::endl;
  B1<A> b2(a1,A());
  std::cout << "3" << std::endl;
  B1<A> b3(A(),a2);
  std::cout << "4" << std::endl;
  B1<A> b4(A(),A());
  std::cout << "5" << std::endl;
  B2<A> b5(a1,a2);
  std::cout << "6" << std::endl;
  B2<A> b6(a1,A());
  std::cout << "7" << std::endl;
  B2<A> b7(A(),a2);
  std::cout << "8" << std::endl;
  B2<A> b8(A(),A());
  std::cout << "9" << std::endl;
  std::cout << std::endl;
  std::cout << "11" << std::endl;
  B1<A> b11(a1,a2);
  std::cout << "12" << std::endl;
  B1<A> b12(a1,inc_a(A()));
  std::cout << "13" << std::endl;
  B1<A> b13(inc_a(A()),a2);
  std::cout << "14" << std::endl;
  B1<A> b14(inc_a(A()),inc_a(A()));
  std::cout << "15" << std::endl;
  B2<A> b15(a1,a2);
  std::cout << "16" << std::endl;
  B2<A> b16(a1,inc_a(A()));
  std::cout << "17" << std::endl;
  B2<A> b17(inc_a(A()),a2);
  std::cout << "18" << std::endl;
  B2<A> b18(inc_a(A()),inc_a(A()));
  std::cout << "19" << std::endl;
}

Which outputs the following:

1
Copy A
Copy A
2
Copy A
Move A
3
Move A
Copy A
4
5
Copy A
Copy A
Move A
Move A
6
Copy A
Move A
Move A
7
Copy A
Move A
Move A
8
9

11
Copy A
Copy A
12
Copy A
Move A
13
Move A
Copy A
14
Move A
Move A
15
Copy A
Copy A
Move A
Move A
16
Move A
Copy A
Move A
Move A
17
Copy A
Move A
Move A
Move A
18
Move A
Move A
Move A
Move A
19

As can be seen, the pass by value approach in B2 causes extra move for each argument in all cases except for when the argument is a prvalue.

If you want best performance, I suggest the template approach in B1. This way you have effectively have separate code for the copy and move cases, and hence only a single copy or a single move required. In the pass by value approach, at least two move/copies are required, except for the prvalue case where the compiler can construct the value in the place of the argument, in which as only one move is required.

Clinton
  • 20,364
  • 13
  • 59
  • 142
-2

Depending on what c++ compiler you are using, you could look into "functions with variable argument lists"

The idea is that you can pass in as many parameters as you want to the method and it just populates into an array that you can loop through.

For microsoft c++, the following blogposts might be helpful:

http://msdn.microsoft.com/en-us/library/fxhdxye9(v=VS.100).aspx http://blogs.msdn.com/b/slippman/archive/2004/02/16/73932.aspx

Devin Garner
  • 1,299
  • 9
  • 19
  • 3
    the number of parameters are fixed (and known). The OP's question is how to exploit move semantics instead of copying the arguments :) – jalf Jul 14 '11 at 06:40
  • NO C++0x program should be using old unsafe varargs, the new C++0x way to do such things is variadic templates, for _typesafe_ functions with variable argument count. Besides that, varargs are totally unrelated to the OPs question. – smerlin Jul 16 '11 at 13:17