Need help with rvalues and move constructor/assignment operators

I have troubles with rvalues and move operations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <unistd.h>
#include <thread>
#include <sys/time.h>

struct V {
    V(V &&v) {
        std::cout << "move constructor\n";
    }

    V(V &v) {
        std::cout << "copy constructor\n";
    }

    V() {
        std::cout << "default constructor\n";
    }

    V &operator=(V &&other) {
        std::cout << "moved\n";
        return *this;
    }

    virtual ~V() {
        std::cout << "Destructor\n";
    }

    int getInt() {
        return 0;
    }
};

V getV() {
    V localV;
    return localV;
}

int main() {
    V v1 = V{}; //1
    V{};//2
    getV(); //3
    int number = getV().getInt();//4
    return 0;
}


As I understand lines that are marked as 2-4 are rvalues so move operators should be used, but copy constructor is always selected.

What is wrong here?
Last edited on
The move constructor is used when constructing an object from an rvalue. It's not necessarily used to construct the rvalue itself. In many situations the compiler is smart enough to not have to call the move constructor at all.


1. V v1 = V{};

V{} is an rvalue so it is possible that v1 will be constructed using the move constructor but modern compilers are smart enough to optimize this code as if you had written V v1{};. In C++17 the compiler isn't even allowed to call the move constructor in this situation.


2. V{};

This always calls the default constructor because you don't pass any arguments to the constructor.


3. getV();

Again, the compiler is smart enough to avoid creating a second object. Instead the function constructs the local variable in place where the return value should be. The compiler would have been able to do this even if you had written something like V v2 = getV();. This is not guaranteed by the standard but it's a relatively easy thing for the compiler to do so there is no excuse for the compiler not to do it.


4. int number = getV().getInt();

This is just like #3. That the returned object is being used doesn't change anything.
Last edited on
To force the copy/move constructor to be called, disable NRVO.

1
2
3
4
5
6
7
8
9
V getV() {

    V localV;

    // *** clang++ warning: moving a local object in a return statement prevents copy elision
    //                note: remove std::move call here
    return std::move(localV); // deliberately disable NRVO
                              // force move construction of result
}

http://coliru.stacked-crooked.com/a/ab68961091cd345c


1
2
3
4
5
6
7
V getV() {

    V localV;

    return static_cast< const V& >(localV); // deliberately disable NRVO
                                            // force copy construction of result
}

http://coliru.stacked-crooked.com/a/de916cdffdbc1ad5
Ok, that makes sense. so my understanding of rvalue was correct but compiler optimization was playing with me.

I am working on arduino project and I suppose its compiler isn't smart enough to do this tricks.
> I am working on arduino project and I suppose its compiler isn't smart enough to do this tricks.

Check it out on your implementation (compile with optimisations enabled).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <string>
#include <cctype>
#include <algorithm>

struct mystring : std::string
{
    using base = std::string ;

    using base::base ;

    mystring( const mystring& that ) : base(that) { ++n_copy_construct ; }
    mystring( mystring&& that ) noexcept : base( std::move(that) ) { ++n_move_construct ; }

    mystring& operator= ( const mystring& ) = default ;
    mystring& operator= ( mystring&& ) noexcept = default ;
    ~mystring() = default ;

    static int n_copy_construct ;
    static int n_move_construct ;
};

int mystring::n_copy_construct = 0 ;
int mystring::n_move_construct = 0 ;

mystring get()
{
    static int n = 0 ;

    mystring mstr = n++ == 1000 ? "ned" : "this should disable any possible small string optimisation!" ;

    for( char& c : mstr ) c = std::toupper(c) ;
    std::reverse( mstr.begin(), mstr.end() ) ;
    std::rotate( mstr.begin(), mstr.begin()+1, mstr.end() ) ;
    mstr += '!' ;
    
    return mstr ;
}

int main()
{
    int cnt = 0 ;
    while( get() != "END!" ) ++cnt ;

    std::cout << "#returns: " << cnt << '\n'
              << "#copy constructs: " << mystring::n_copy_construct << '\n'
              << "#move constructs: " << mystring::n_move_construct << '\n' ;

    if( mystring::n_copy_construct == 0 && mystring::n_move_construct == 0 )
        std::cout << "\n*** hurrah! NRVO was applied. ***\n" ;
}

http://coliru.stacked-crooked.com/a/d89889b1cc2a0913
http://rextester.com/WGL43992
Topic archived. No new replies allowed.