我有一个类A
,可以在构造/复制/移动时打印出一条消息
class A
{
public:
A(std::string s)
:s_(s)
{
std::cout << "A constructed\n";
}
~A()
{
std::cout << "A destructed\n";
}
A(const A& a)
:s_(a.s_)
{
std::cout << "A copy constructed\n";
}
A(A&& a)
:s_(std::move(a.s_))
{
std::cout << "A moved\n";
}
A& operator=(const A& a)
{
s_ = a.s_;
std::cout << "A copy assigned\n";
}
A& operator=(A&& a)
{
s_ = std::move(a.s_);
std::cout << "A move assigned\n";
}
std::string s_;
};
在main
中,我构造了一个A
的实例,按值在lambda中捕获,将该lambda复制到std::function
,最后移动将std::function
转换为另一个std::function
int main()
{
A a("hello ");
std::function<void()> f = [a]{ std::cout << a.s_; };
std::function<void()> g(std::move(f));
}
这打印出以下
A constructed
A copy constructed
A copy constructed
A destructed
A destructed
A destructed
为什么A
的移动构造函数没有被调用?将f
移动到g
的最后一步是不是已调用A
的移动构造函数?
答案 0 :(得分:6)
由于您移动了std::function
,因此未精确调用复制构造函数。这是因为std::function
可以选择将捕获的值存储在堆上并保留指向它们的指针。因此,移动该函数只需要移动该内部指针。显然,MSVC选择将捕获存储在堆和GCC等上。选择将它们存储在堆栈上,因此也需要移动捕获的值。
编辑:感谢Mooing Duck在comment on the question中指出GCC还在堆上存储捕获。实际差异似乎是当从lambda构造时,GCC将捕获从lambda移动到std::function
。
答案 1 :(得分:3)
在这种情况下,您的标准库实现不使用小缓冲区优化,
因此,您的函数f
包含指向堆分配的内存区域的指针,其中存储了a
的副本。由于您将f
移至g
,因此没有理由执行深层复制,并且实现只需将f
中存储的函数的所有权移至g
(比如unique_ptr
)。
至于这里没有使用小缓冲区的原因,这个可能与你的实现将function
移动构造函数定义为 noexcept <强> †
如果function
的移动构造函数是noexcept,则它不能调用任何可能抛出的函数,因此实现只是拒绝移动您的对象(从f
的小缓冲区移动到{ {1}}是一个)并在堆上分配它,这样它就可以在移动构造函数/赋值中移动一个指针。
如果您只是将g
添加到libstd++
,libc++
和g = move(f)
都会在noexcept
行生成复制构造函数调用的复制构造函数。令人惊讶的是,他们似乎都忽略了A
移动构造函数的存在。
†请注意(至少在最新草案中)标准要求noexcept
为非noexcept ,但libstd ++和libc ++都将其实现为noexcept,我可以'现在请查看MSVC。
答案 2 :(得分:1)
这似乎是MSVC std::function
移动构造函数的弱点。我在Clang 3.3上尝试了你的代码,它调用了A
的移动构造函数。