Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to use emplace_back to avoid move constructor call?

I just learned about guaranteed copy elision in C++17. According to the answer on that question:

When you do return T();, this initializes the return value of the function via a prvalue. Since that function returns T, no temporary is created; the initialization of the prvalue simply directly initializes the return value.

The thing to understand is that, since the return value is a prvalue, it is not an object yet. It is merely an initializer for an object, just like T() is.

So I was wondering, does this work for anything other than:

T f() {return T();}
T t = f();

So I wrote this code with emplace_back to test it:

#include <vector>
#include <iostream>
struct BigObj{
  BigObj() = default;
  BigObj(int) { std::cout << "int ctor called" << std::endl;  }
  BigObj(const BigObj&){
    std::cout << "copy ctor called" << std::endl;
  }
  BigObj(BigObj&&){
    std::cout << "move ctor called" << std::endl;
  }
};
BigObj f(){ return BigObj(2); }
int g(){ return 2; }
int main(){
  std::vector<BigObj> v;
  v.reserve(10);
  std::cout << "emplace_back with rvalue \n";
  v.emplace_back(1+1);
  std::cout << "emplace_back with f()\n";
  v.emplace_back(f());
  std::cout << "emplace_back with g()\n";
  v.emplace_back(g());
}

This is the output I get (with copy elision disabled):

emplace_back with rvalue 
int ctor called
emplace_back with f()
int ctor called
move ctor called
emplace_back with g()
int ctor called

It seems that the move constructor is still called even though a prvalue is passed directly into emplace_back, which I thought could be used to directly construct an object instead of being used to construct a temporary and then moving it.

Is there a more elegant way to avoid the move constructor call with emplace_back other than doing something like what the g() function does?

like image 457
user2108462 Avatar asked Apr 10 '18 08:04

user2108462


Video Answer


1 Answers

It seems that the move constructor is still called even though a prvalue is passed directly into emplace_back

You think you do, but you don't pass it into the function. You give it a prvalue as argument, yes, but what emplace_back accepts is a pack of forwarding references. A reference must refer to an object, so a temporary is materialized, and moved.

The correct way to use emplace_back is to pass it the arguments for initializing the object in place. That way you don't need to move the element type of the vector (though you may need to move/copy the arguments).

like image 60
StoryTeller - Unslander Monica Avatar answered Oct 11 '22 17:10

StoryTeller - Unslander Monica