为什么 make_unique 有一个可以将 std::bind 作为参数的构造函数的额外移动?
Why does make_unique have an extra move with a constructor that can take std::bind as an argument?
我有一个简单的 class,它的构造函数如下所示:
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)));
以上述方式使用它会导致 'thing' 的一个副本构造,然后在该副本上进行移动构造。
但是,执行以下操作:
std::unique_ptr<Event> ev = make_unique<Event>(std::bind(some_func,thing));
导致两个移动结构。我的问题是:
- 'thing' 上的移动构造函数究竟是什么时候调用的
- 为什么用 make_unique 调用了两次?
这是最简单的例子:
#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)
我认为使用 make_unique
时的额外移动是由于 移动省略 在 Event(std::bind(lambda,thing))
.
被调用的Event
的构造函数是Event(function<void()> && f)
,所以必须创建一个临时的function<void()>
。此临时文件使用 std::bind
表达式的 return 值进行初始化。
用于执行此从 std::bind
的 return 类型到 std::function<void()>
类型的转换的构造函数采用参数 by value:
template<class F> function(F f); // ctor
这意味着我们必须将std::bind
的return值移动到function<void()>
的构造函数的这个参数f
。但是该移动符合 移动省略.
当我们通过 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 (+)
我有一个简单的 class,它的构造函数如下所示:
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)));
以上述方式使用它会导致 'thing' 的一个副本构造,然后在该副本上进行移动构造。
但是,执行以下操作:
std::unique_ptr<Event> ev = make_unique<Event>(std::bind(some_func,thing));
导致两个移动结构。我的问题是:
- 'thing' 上的移动构造函数究竟是什么时候调用的
- 为什么用 make_unique 调用了两次?
这是最简单的例子:
#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)
我认为使用 make_unique
时的额外移动是由于 移动省略 在 Event(std::bind(lambda,thing))
.
被调用的Event
的构造函数是Event(function<void()> && f)
,所以必须创建一个临时的function<void()>
。此临时文件使用 std::bind
表达式的 return 值进行初始化。
用于执行此从 std::bind
的 return 类型到 std::function<void()>
类型的转换的构造函数采用参数 by value:
template<class F> function(F f); // ctor
这意味着我们必须将std::bind
的return值移动到function<void()>
的构造函数的这个参数f
。但是该移动符合 移动省略.
当我们通过 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 (+)