51

It seems the main advice concerning C++0x's rvalues is to add move constructors and move operators to your classes, until compilers default-implement them.

But waiting is a losing strategy if you use VC10, because automatic generation probably won't be here until VC10 SP1, or in worst case, VC11. Likely, the wait for this will be measured in years.

Here lies my problem. Writing all this duplicate code is not fun. And it's unpleasant to look at. But this is a burden well received, for those classes deemed slow. Not so for the hundreds, if not thousands, of smaller classes.

::sighs:: C++0x was supposed to let me write less code, not more!

And then I had a thought. Shared by many, I would guess.

Why not just pass everything by value? Won't std::move + copy elision make this nearly optimal?

Example 1 - Typical Pre-0x constructor

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // single copy
OurClass(SomeClass());  // single copy

Cons: A wasted copy for rvalues.

Example 2 - Recommended C++0x?

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}
OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // zero copies, one move
OurClass(SomeClass());  // zero copies, one move

Pros: Presumably the fastest.
Cons: Lots of code!

Example 3 - Pass-by-value + std::move

OurClass::OurClass(SomeClass obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy, one move
OurClass(std::move(o)); // zero copies, two moves
OurClass(SomeClass());  // zero copies, one move

Pros: No additional code.
Cons: A wasted move in cases 1 & 2. Performance will suffer greatly if SomeClass has no move constructor.


What do you think? Is this correct? Is the incurred move a generally acceptable loss when compared to the benefit of code reduction?

Alex Bitek
  • 6,529
  • 5
  • 47
  • 77
dean
  • 51
  • 1
  • 4
  • 8
  • 5
    You are [not the first](http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) to think of this ;-) – fredoverflow May 15 '10 at 09:47
  • 2
    @dean i will do the by-value-and-then-move in my code if i *know* the argument has a move constructor. If i don't know, i will do the overload-on-rvalue-ref dance. – Johannes Schaub - litb May 15 '10 at 10:30
  • 1
    @FredOverflow: I had read that article. I like it, but it is mistaken in some parts ("at worst, performance will be no worse") and too theoretical for my taste -- no measurements of any kind. In any case, my post wasn't meant to suggest something new, but to ask the question: Which will you prefer? Obviously this depends on task and person, but I was interested in responses. (BTW, I now wonder if the compiler can eliminate the extra assignments in #3.) – dean May 15 '10 at 10:45
  • @dean see the discussions in the comments to this answer: http://stackoverflow.com/questions/2794369/template-class-ctor-against-function-new-c-standard/2794527#2794527 – Johannes Schaub - litb May 15 '10 at 10:47
  • 2
    @dean i don't think it's mistaken by saying "at worst, performance will be no worse". It's exactly right. If you have to copy anyway later, then you can omit it, and modify the parameter directly. I don't think that statement was meant to apply to this copy-to-member case, where indeed performance can be a little bit worse in some cases. – Johannes Schaub - litb May 15 '10 at 10:50
  • @litb: Unless my reasoning is flawed, passing an lvalue through `std::move` to a function will still generate an extra move, that a reference argument would not. But this is a laughable nitpick, not something to lose sleep over! :) – dean May 15 '10 at 11:29
  • @dean: Dave doesn't consider your case in his article. You want to copy construct a data member. Dave's talking about function-local copies. The copy & swap idiom is one example of this. The copy has to be made anyways and the idea is to let the compiler worry about that (and possibly elide it). I agree with Johannes. If I'm sure that objects of the type in question move fast or I'm just lazy, I won't bother overloading on the "valueness". In generic code (like vector<>::push_back) I'll probably overload... – sellibitze May 15 '10 at 20:08
  • @sellibitze: I think you missed my last response. Let's modify Dave's example a bit. `auto sorted(vector names) { /*no-op*/ } vector vec(10); /* do something */ sorted(std::move(vec));` In VC10, this generates a call to `vector`'s move constructor. If sorted is declared as `sorted(vector&& names)` then this does not happen. So I think it stands to reason, that an lvalue (or rvalue) reference is (minutely) better under this circumstance. – dean May 16 '10 at 00:37
  • In example 3, `OurClass(SomeClass());` technically involves two moves. Not that it matters. – aschepler Aug 07 '14 at 12:00
  • Note that even if you're right, this argument only applies to constructors. When you try to apply this to anything else (operator=), you end up with unnecessary copies. – Mooing Duck Aug 12 '16 at 17:51

1 Answers1

8

I was interested in your question because I was new to the topic and did some research. Let me present the results.

First, your sigh.

::sighs:: C++0x was supposed to let me write less code, not more!

what it is also supposed is to give you a better control over the code. And that it does. I would stick to an extra constructor:

OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

I personally prefer verbosity in complex and important situations, because it keeps me and possible readers of my code alerted.

take, for example, the C-style cast (T*)pT and C++ standard static_cast<T*>(pT) Much more verbose - but a big step forward.

Second, I was a bit suspicious about your Example 3, last test case. I thought there might be another move constructor involved to create the passed-by-value parameter from the rvalue. So i have created some quick project in my new VS2010 and got some clarifications. I will post the code here as well as results.

the source:

// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <utility>
#include <iostream>

class SomeClass{
    mutable int *pVal;
public:
    int Val() const { return *pVal; };
    SomeClass(int val){
        pVal = new int(val);
        std::cout << "SomeClass constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    SomeClass(const SomeClass& r){
        pVal = new int(r.Val());
        std::cout << "SomeClass copy constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }   
    SomeClass(const SomeClass&& r){
        pVal = r.pVal;
        r.pVal = 0;
        std::cout << "SomeClass move constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    ~SomeClass(){
        if(pVal)
            delete pVal;
        std::cout << "SomeClass destructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
};

class OtherClass{
    SomeClass sc;
public:
    OtherClass(int val):sc(val){
    }

Note this secion:

#if 1
    OtherClass(SomeClass r):sc(std::move(r)){
    }
#else
    OtherClass(const SomeClass& r):sc(r){
    }   
    OtherClass(const SomeClass&& r):sc(std::move(r)){
    }
#endif

...

    int Val(){ return sc.Val(); }
    ~OtherClass(){
    }
};

#define ECHO(expr)  std::cout << std::endl << "line " << __LINE__ << ":\t" #expr ":" << std::endl; expr

int _tmain(int argc, _TCHAR* argv[])
{
    volatile int __dummy = 0;
    ECHO(SomeClass o(10));

    ECHO(OtherClass oo1(o));            
    __dummy += oo1.Val();
    ECHO(OtherClass oo2(std::move(o))); 
    __dummy += oo2.Val();
    ECHO(OtherClass oo3(SomeClass(20)));  
    __dummy += oo3.Val();

    ECHO(std::cout << __dummy << std::endl);
    ECHO(return 0);
}

As you have noted, there is a compile-time switch that allows me to test the two approaches.

The results are best viewed in text compare mode, on the left you can see the #if 1 compilation, meaning that we check the proposed workaround, and on the right - #if 0, meaning that we check the "kosher" way described in the c++0x!

I was wrong suspecting the compiler do stupid things; it saved the extra move constructor in the third test case.

But to be honest, we have to account for another two destructors being called in the proposed workaround, but this is for sure a minor drawback taking into account that no actions should be performed if a move has occurred on the object being destructed. Still, it is good to know.

In any case, I am not leaving the point that it is better to write one other constructor in the wrapper class. It is just a matter of several lines, since all the tedious work is already done in the SomeClass that has to have a move constructor.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
ULysses
  • 978
  • 4
  • 9
  • +1 for the effort and the point of view regarding the value of explicit code. – neuro Jun 10 '10 at 13:31
  • Extra move constructors are not really a concern. As long as you avoid unnecessary copy constructors, things will be pretty good. `SomeClass(const SomeClass&& r)` shouldn't compile... – aschepler Aug 07 '14 at 11:56
  • `__dummy` should be `_dummy` because of two underscores >o – ikh Aug 07 '14 at 12:48