我有一个简单的类,其构造函数如下:
Event(std::function<void()> &&f) : m_f(std::move(f)) { }
构造函数可以与std :: bind:
一起使用Thing thing;
std::unique_ptr<Event> ev(new Event(std::bind(some_func,thing)));
以上述方式使用它会导致一个副本构造“&#39;”,然后在该副本上移动构造。
但是,请执行以下操作:
std::unique_ptr<Event> ev = make_unique<Event>(std::bind(some_func,thing));
两个移动结构的结果。我的问题是:
这是最小的例子:
#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;
}
输出:
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:我用C ++ 11和14标记了这个问题,因为使用常见的make_unique函数将C ++ 11标志传递给gcc时会出现同样的问题(make_unique and perfect forwarding)
答案 0 :(得分:9)
我认为使用make_unique
时的额外举动是由于Event(std::bind(lambda,thing))
中的移动省略。
被调用的Event
的构造函数是Event(function<void()> && f)
,因此必须创建临时function<void()>
。使用std::bind
表达式的返回值初始化此临时值。
用于执行此转换的构造函数从返回类型std::bind
到std::function<void()>
接受参数按值:
template<class F> function(F f); // ctor
这意味着我们必须将std::bind
的返回值移动到f
的构造函数的此参数function<void()>
。但是这一举动符合 move elision 的条件。
当我们通过make_unique
传递该临时值时,它已被绑定到引用并且移动省略可能不再适用。如果我们因此压制移动省略:
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";
(我们可以使用std::move
作为suppress_elision
的实现。)
我们可以观察到相同数量的移动:Live example
解释整套操作:
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 (+)
(*)可以省略
(+)但不是,可能是因为内部函数对象在堆上,只有一个指针被移动。 Verify by replacing m_f(std::move(f))
with m_f()
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 (+)