Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload on reference, versus sole pass-by-value + std::move?

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.