3

Right when I thought I understood what std::move and move constructors do, I tried to write some unit test, actually testing the move constructor for some class...

To my surprise I found, that I cannot think of a way to construct code which actually calls the move constructor. Worse, I cannot even set a breakpoint in the body of the move constructor (in VS2013 community edition, debug, 64bit build).

Wondering if this is a compiler peculiarity, I knocked up some small test code on my freebsd virtual machine, using clang (3.4.1). There, too I fail to find a way to get that move constructor invoked.

#include <iostream>
#include <stdint.h>
#include <string>
#include <algorithm>
#include <functional>
#include <ctype.h>
#include <locale>

void InPlaceToUpper( std::string& target )
{
    std::transform(target.begin(), target.end(), target.begin(), ::toupper);
}
void InPlaceToLower( std::string& target )
{
    std::transform(target.begin(), target.end(), target.begin(), ::tolower);
}

std::string ToUpper( const std::string& s )
{
    std::string result;
    result.resize(s.length());
    std::transform(s.begin(), s.end(), result.begin(), ::toupper);
    return result;
}
std::string ToLower( const std::string& s)
{
    std::string result;
    result.resize(s.length());
    std::transform(s.begin(), s.end(), result.begin(), ::tolower);
    return result;
}


class CFoo
{
    std::string m_value;
public:
    CFoo()
        : m_value()
    {
        std::cout << "CFoo() called." << std::endl;
    }
    CFoo(const char *value)
        : m_value(value)
    {
        std::cout << "CFoo(const char *) called." << std::endl;
    }
    CFoo(const std::string& value )
         : m_value(value)
    {
        std::cout << "CFoo(const std::string&) called." << std::endl;
    }
    CFoo(const CFoo& other )
        : m_value(other.m_value)
    {
        std::cout << "CFoo() copy constructor called." << std::endl;
    }
    CFoo(CFoo&& other )
        : m_value(std::move(other.m_value))
    {
        std::cout << "CFoo() move constructor called." << std::endl;
        std::cout << "other.m_value = " << other.m_value.c_str() << std::endl;
    }
    ~CFoo()
    {
        std::cout << "~CFoo() called." << std::endl;
    }
    const CFoo& operator=( const CFoo& other )
    {
        std::cout << "CFoo copy assignment operator called." << std::endl;
        if( &other != this )
        {
            m_value = other.m_value;
        }
        return *this;
    }
    const CFoo& operator=( CFoo&& other )
    {
        std::cout << "CFoo move assignment operator called." << std::endl;
        if( &other != this )
        {
            m_value = std::move(other.m_value);
        }
        return *this;
    }
    CFoo ToUpper()
    {
        return CFoo(::ToUpper(m_value));
    }
    CFoo ToLower()
    {
        return CFoo(::ToLower(m_value));
    }
    const char * ToString() const
    {
        return m_value.c_str();
    }
};

int main( int argc, const char *argv[] )
{
    {
        CFoo foo;
        CFoo foo1("Hello World");
        CFoo foo2 = CFoo("Hello again World!");
        CFoo foo3(CFoo("Bye world"));
        CFoo foo4 = CFoo("Bye again world");
        CFoo foo5 = foo4.ToUpper();
        CFoo foo6 = foo4.ToLower();

        foo6 = foo4.ToUpper();
        std::cout << "foo4: " <<  foo4.ToString() << std::endl;
        foo6 = CFoo("Well well well");
    }
    return 0;
}

My apologies if the code is not as short as it might possibly be. But there are only a few spots to look at, namely my efforts to get the move constructor invoked in main() and the definition of the various constructors in class Foo.

I am aware of compiler settings which allow turning off RVO and stuff but for the purpose of using the feature "move constructor" in performance aware code, there should be a classic example of when it gets invoked. If that is not the case, I will probably decide not to even bother using move constructors at all.

To answer the question, you can tell me a line I can write in main() which gets the move constructor of CFoo called. Or you can tell me what I am doing wrong.
Does std::string support being moved like that? Maybe this is why my efforts fail?

Thanks, in advance.

BitTickler
  • 10,905
  • 5
  • 32
  • 53

2 Answers2

8

In all your attempts to use the move constructor it is being elided, so that e.g. CFoo foo = CFoo(blah); is simply equivalent to CFoo foo(blah); which doesn't need to use the move constructor. This is a Good Thing because the compiler is optimising away the need for any copy or move to happen at all.

To see the move constructor being used try:

CFoo f1;
CFoo f2 = std::move(f1);

This constructs f2 from an rvalue, and nothing can be elided, so the move constructor will be used.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Reproduced this and actually it calls the move constructor (Yay!). But what does that mean for the CFoo.ToLower() functions? Do I also need std::move() around the call of those? – BitTickler Feb 13 '15 at 13:09
  • 4
    No! That would make it slower, don't do that! Currently the compiler is performing elision that turns "construct temporary + move construction + destroy temporary " into just "construct object", which is a Good Thing. If you force it to use the move constructor you force it to perform extra work. Skipping an unnecessary construction completely is even faster than moving. – Jonathan Wakely Feb 13 '15 at 13:11
  • 1
    See also [RVO](http://stackoverflow.com/questions/17473753/c11-return-value-optimization-or-move) – Chris Drew Feb 13 '15 at 13:14
  • 4
    Basically the compiler knows what it's doing and is performing move elision to optimise your code. If you interfere and force it to move, because you think that's more efficient, you actually make things worse. Turning copies into moves is good, but introducing additional moves where no copy or move is needed is bad. – Jonathan Wakely Feb 13 '15 at 13:19
1

First of all, there is an error in std::string ToUpper(const std::string& s), namely there is no space in result. C++ algorithms do not grow their target containers automatically. You must either allocate space yourself, or use a inserter adaptor.

To make space in out, e.g. do this:

result.resize(s.length());

After it the move assignment operator called for:

  • foo6 = foo4.ToUpper();
  • foo6 = CFoo("Well well well");

The move constructor is called whenever an object is initialized from xvalue of the same type, which includes:

  • initialization, T a = std::move(b); or T a(std::move(b));, where b is of type T
  • function argument passing: f(std::move(a));, where a is of type T and f is void f(T t)
  • function return: return a; inside a function such as T f(), where a is of type T which has a move constructor.

For more see move constructors on cppreference.com.

Kornel
  • 5,264
  • 2
  • 21
  • 28
  • Good you point that out. I copied that part from another stack-overflow answer. Noone pointed that out, there :) – BitTickler Feb 13 '15 at 13:07