Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why wasn't std::string move constructor called?

Tags:

c++

I have this example:

#include <string>
#include <iostream>

class Test {
private:
    std::string str;
public:
    Test(std::string &&str_) :
        str(str_)
    {}

    const std::string &GetStr()
    {
        return str;
    }
};

int main(int argc, char *argv[])
{
    std::string there("1234567890");
    std::cout << "1. there: " << there << '\n';

    Test t1(std::move(there));

    std::cout << "2. there: " << there << '\n';
    std::cout << "3. there: " << t1.GetStr() << '\n';
}

It gives the output

$ ./a.out
1. there: 1234567890
2. there: 1234567890
3. there: 1234567890

This is using gcc 5.1.1 on linux. While the there string will be left in valid but indeterminate state after the move, this implementation seems to move (and not copy) the string if the std::string move constructor is invoked.

if I replace the initalizer str(str_) with str(std::move(str_)) I get this output:

$ ./a.out
1. there: 1234567890
2. there: 
3. there: 1234567890 

This suggest the std::string move constructor is now used, but why isn't std::string(std::string &&) invoked in my first example ?

like image 861
binary01 Avatar asked Nov 02 '15 08:11

binary01


2 Answers

You should do

public:
    Test(std::string &&str_) :
        str(std::move(str_))
    {}

str_ do have a name, is a named object, so it won't be passed to any function as an rvalue-reference.

A design choice made by the Standard committee prevents it to be treated as an rvalue, so you can't inadvertently have it modified. In particular: the type of str_ do is an lvalue reference to string, but str_ is not considered an rvalue, because it's a named object.

You must make your intention explicit by adding a call to std::move. Doing so you state that you want str_ to be an rvalue and you know all the consequences of this choice.

like image 167
Paolo M Avatar answered Sep 22 '22 16:09

Paolo M


Because lvalue-reference always wins! That's why you need to explicitly specify std::move.

It is permitted to form references to references through type manipulations in templates or typedefs, in which case the reference collapsing rules apply: rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference:

typedef int&  lref;
typedef int&& rref;
int n;
lref&  r1 = n; // type of r1 is int&
lref&& r2 = n; // type of r2 is int&
rref&  r3 = n; // type of r3 is int&
rref&& r4 = 1; // type of r4 is int&&

Taken from here.

like image 43
stas.yaranov Avatar answered Sep 24 '22 16:09

stas.yaranov