fetag
fetag

Reputation: 174

low efficient std::move()?

Please take a look at the following code snippet, which seems std::move() is low efficient in this scenario.

class A {};

struct B {
    double pi{ 3.14 };
    int    i{ 100 };
    A* pa{ nullptr };
};

int main() {
    B b;
    std::vector<B> vec;
    vec.emplace_back(b);                // 1) without move
    vec.emplace_back(std::move(b));     // 2) with move
    return 0;
}

I got the following disassembly in visual studio 2019 [C++ 14, Release]:

    vec.emplace_back(b);                // 1) without move
00E511D1  push        eax  
00E511D2  push        0  
00E511D4  lea         ecx,[vec]  
00E511D7  call        std::vector<B,std::allocator<B> >::_Emplace_reallocate<B> (0E512C0h)  
    vec.emplace_back(std::move(b));     // 2) with move
00E511DC  mov         eax,dword ptr [ebp-18h]  
00E511DF  cmp         eax,dword ptr [ebp-14h]  
00E511E2  je          main+91h (0E511F1h)  
00E511E4  movups      xmm0,xmmword ptr [b]  
00E511E8  movups      xmmword ptr [eax],xmm0  
00E511EB  add         dword ptr [ebp-18h],10h  
00E511EF  jmp         main+9Eh (0E511FEh)  
00E511F1  lea         ecx,[b]  
00E511F4  push        ecx  
00E511F5  push        eax  
00E511F6  lea         ecx,[vec]  
00E511F9  call        std::vector<B,std::allocator<B> >::_Emplace_reallocate<B> (0E512C0h)  

It's easy to see that the move version takes more unnecessary work. According to the description here, the compiler will generate a trivial move constructor for struct B and this trivial move constructor will take a copy semantic.

Then my questions are:

  1. std::move() is completely redundant for this case.
  2. Moreover, if the parameter of std::move() has a trivial move constructor, then std::move() is redundant.
  3. if the trivial move constructor performs the same action as the trivial copy constructor, why the compiler generates different disassembly? Actually, this is the most confusing for me.

Upvotes: 2

Views: 325

Answers (2)

fetag
fetag

Reputation: 174

Well, I would like to make a summary of this question.

  1. if the move constructor is trivial (generated by the compiler), then std::move() only takes a bitwise copy. Which means vec.emplace_back(std::move(b)) is equivalent to vec.emplace_back(b).

  2. following is another better example to show the difference. We try to construct both members _x and _y with move semantic. Since X has a user-declared move ctor but Y hasn't, X's move ctor gets called, and Y's copy ctor gets called.

class X {
public:
    X() { cout << "X()" << endl; }
    X(X const&) { cout << "X(X const&)" << endl; }
    X(X&&) noexcept { cout << "X(X&&)" << endl; }
};

class Y {
public:
    Y() { cout << "Y()" << endl; }
    Y(Y const&) { cout << "Y(Y const&)" << endl; }
};

struct Box {
    Box(X&& x, Y&& y) : _x(std::move(x)), _y(std::move(y)) {}
    X _x;
    Y _y;
};

int main() {
    X x;
    Y y;
    Box bb(std::move(x), std::move(y));
    return 0;
}

/*
output:
X()
Y()
X(X&&)
Y(Y const&)
*/

Upvotes: 0

eerorika
eerorika

Reputation: 238311

  1. std::move() is completely redundant for this case.

Technically not a question but yes, this is correct.

  1. Moreover, if the parameter of std::move() has a trivial move constructor, then std::move() is redundant.

Same as above.

  1. if the trivial move constructor performs the same action as the trivial copy constructor, why the compiler generates different disassembly?

Possibly because you call the function std::move in one but not the other.

It doesn't have to produce different assembly however since the observable behaviour is identical. My compiler produces identical assembly.

Upvotes: 6

Related Questions