我有一个琐碎的类,它的构造函数如下所示:
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)));
以上述方式使用它会导致“物”的一个拷贝构造,然后在该拷贝上进行移动构造。
但是,请执行以下操作:
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函数(make_unique and perfect forwarding)将C++ 11标志传递给gcc时,会发生相同的问题
最佳答案
我认为使用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()>
。但是该举动有资格进行举动省略。当我们通过
make_unique
传递该临时变量时,它已绑定(bind)到引用,并且可能不再应用移动省略。因此,如果我们抑制移动省略: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))
:操作|行为
-------------------------------------------------- ---- + ----------
`thing`变量->`bind`临时|拷贝
`bind`临时->`function` ctor参数| Action (*)
`function` ctor param->`function`对象(临时)| Action
`function`临时->`Event` ctor ref参数| --
`Event` ctor ref param->`function`数据成员| *可以*移动(+)
(*)可被忽略
(+),但不是,可能是因为内部函数对象在堆上,并且仅移动了指针。 Verify by replacing
m_f(std::move(f))
with m_f()
。对于
make_unique<Event>(std::bind(lambda,thing))
:操作|行为
-------------------------------------------------- ------ + ----------
`thing`变量->`bind`临时|拷贝
`bind`临时->`make_unique` ref参数| --
`make_unique` ref param->`function` ctor param | Action
`function` ctor param->`function`对象(临时)| Action
`function`临时->`Event` ctor ref参数| --
`Event` ctor ref param->`function`数据成员| *可以*移动(+)
关于c++ - 为什么make_unique可以使用std::bind作为参数的构造函数有额外的 Action ?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27952709/