I have a trivial class that has a constructor that looks like:
Event(std::function<void()> &&f) : m_f(std::move(f)) { }
The constructor can be used with std::bind:
Thing thing;
std::unique_ptr<Event> ev(new Event(std::bind(some_func,thing)));
Using it in the manner above results in one copy construction of 'thing', then a move construction on that copy.
However, doing the following:
std::unique_ptr<Event> ev = make_unique<Event>(std::bind(some_func,thing));
Results in two move constructions. My questions are:
Here's the minimal example:
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
class Thing
{
public:
Thing() : x(0)
{
}
Thing(Thing const &other)
{
this->x = other.x;
std::cout << "Copy constructed Thing!\n";
}
Thing(Thing &&other)
{
this->x = other.x;
std::cout << "Move constructed Thing!\n";
}
Thing & operator = (Thing const &other)
{
this->x = other.x;
std::cout << "Copied Thing!\n";
return (*this);
}
Thing & operator = (Thing && other)
{
this->x = other.x;
std::cout << "Moved Thing!\n";
return (*this);
}
int x;
};
class Event
{
public:
Event() { }
Event(function<void()> && f) : m_f(std::move(f)) { }
void SetF(function<void()> && f) { m_f = std::move(f); }
private:
function<void()> m_f;
};
int main() {
auto lambda = [](Thing &thing) { std::cout << thing.x << "\n"; };
Thing thing;
std::cout << "without unique_ptr: \n";
Event ev(std::bind(lambda,thing));
std::cout << "\n";
std::cout << "with unique_ptr, no make_unique\n";
unique_ptr<Event> ev_p(new Event(std::bind(lambda,thing)));
std::cout << "\n";
std::cout << "with make_unique: \n";
auto ev_ptr = make_unique<Event>(std::bind(lambda,thing));
std::cout << "\n";
std::cout << "with SetF: \n";
ev_ptr.reset(nullptr);
ev_ptr = make_unique<Event>();
ev_ptr->SetF(std::bind(lambda,thing));
std::cout << "\n";
return 0;
}
With output:
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
or
clang++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
without unique_ptr:
Copy constructed Thing!
Move constructed Thing!
with unique_ptr, no make_unique
Copy constructed Thing!
Move constructed Thing!
with make_unique:
Copy constructed Thing!
Move constructed Thing!
Move constructed Thing!
with SetF:
Copy constructed Thing!
Move constructed Thing!
PS: I tagged this question with C++11 as well as 14 because the same issue occurs when passing the C++11 flag to gcc with the commonly used make_unique function found here (make_unique and perfect forwarding)
I think the additional move when using make_unique
is due to move elision in Event(std::bind(lambda,thing))
.
The constructor of Event
that is called is Event(function<void()> && f)
, so a temporary function<void()>
has to be created. This temporary is initialized with the return value of the std::bind
expression.
The constructor used to perform this conversion from the return type of std::bind
to std::function<void()>
takes the argument by value:
template<class F> function(F f); // ctor
This means we have to move the return value of std::bind
to this parameter f
of the constructor of function<void()>
. But that move is eligible for move elision.
When we pass that temporary through make_unique
, it has been bound to a reference and move elision may not be applied any more. If we therefore suppress move elision:
std::cout << "with unique_ptr, no make_unique\n";
unique_ptr<Event> ev_p(new Event(suppress_elision(std::bind(lambda,thing))));
std::cout << "\n";
std::cout << "with make_unique: \n";
auto ev_ptr = make_unique<Event>(suppress_elision(std::bind(lambda,thing)));
std::cout << "\n";
(We can use std::move
as the implementation of suppress_elision
.)
We can observe the same number of moves: Live example
Explaining the whole set of operations:
For new Event(std::bind(lambda,thing))
:
operation | behaviour ------------------------------------------------------+---------- `thing` variable -> `bind` temporary | copies `bind` temporary -> `function` ctor param | moves (*) `function` ctor param -> `function` object (temp) | moves `function` temporary -> `Event` ctor ref param | - `Event` ctor ref param -> `function` data member | *can* move (+)
(*) can be elided
(+) but doesn't, probably because the internal function object is on the heap, and only a pointer is moved. Verify by replacing m_f(std::move(f))
with m_f()
.
For make_unique<Event>(std::bind(lambda,thing))
:
operation | behaviour --------------------------------------------------------+---------- `thing` variable -> `bind` temporary | copies `bind` temporary -> `make_unique` ref param | - `make_unique` ref param -> `function` ctor param | moves `function` ctor param -> `function` object (temp) | moves `function` temporary -> `Event` ctor ref param | - `Event` ctor ref param -> `function` data member | *can* move (+)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With