我正在尝试将我的代码从linux移植到Windows。但是在Visual Studio中,我的代码崩溃并出现以下错误:
Microsoft C ++异常:内存位置的std :: bad_function_call
这是我的代码:
#include <functional>
class Foo {
public:
Foo(int) : m_deleter{ []() {} } {}
Foo(const Foo &) = delete;
Foo(Foo &&) = default;
Foo & operator=(const Foo &) = delete;
Foo & operator=(Foo &&) = default;
~Foo()
{
m_deleter();
}
private:
std::function<void()> m_deleter;
};
int main() {
Foo foo(1);
Foo bar(2);
std::swap(foo, bar);
}
使用std::swap
时崩溃了。在linux中,它完美无缺。
奇怪的是,当我通过GCC尝试compile it online时,它也无法工作。我做错了什么,为什么在家里与Clang(3.5)一起工作。
编辑:事实证明它与Visual Studio 2015和GCC 4.9.2崩溃,但与Clang 3.5没有崩溃。
答案 0 :(得分:3)
行为的原因很简单;在m_deleter
的析构函数中无条件地调用Foo
,即使在它不可调用的情况下也是如此。
std::swap
的直接实现会创建一个临时表来保存两个操作数之一的中间结果,这个临时表示不会有可调用的m_deleter
。
什么是 std :: bad_function_call ?
如果您尝试调用没有有效目标的std::bad_function_call,则会引发std::function
。
我们可以将 testcase 缩减为以下更明确的代码段:
1 #include <functional>
2 #include <utility>
3 struct A {
4 A ()
5 : _cb {[]{}}
6 { }
7
8 A (A&& src)
9 : _cb (std::move (src._cb))
10 { }
11
12 A& operator= (A&& src)
13 {
14 _cb = std::move (src._cb);
15 return *this;
16 }
17
18
19 ~A () {
20 _cb ();
21 }
22
23 std::function<void()> _cb;
24 };
25 void swap (A& lhs, A& rhs) {
26 A temporary = std::move (lhs);
27 lhs = std::move (rhs);
28 rhs = std::move (temporary);
29 }
30 int main() {
31 A x, y;
32 swap (x, y);
33 }
问题
离开swap
时,临时将被销毁,而后者又会尝试调用_cb
- 问题是temporary._cb
已被移动-from 在线 14 ;它不再可调用,抛出异常。
解决方案
~A::A () {
if (_cb) // check if callable
_cb ();
}
答案 1 :(得分:2)
std::swap()
中使用临时对象。当swap()
返回时,临时对象的m_deleter
为空。当临时破坏时,m_deleter();
抛出std::bad_function_call
m_deleter
没有目标。
我机器上的std::swap
(gcc4.9.1,ubuntu)如下所示:
template<typename _Tp>
inline void
swap(_Tp& __a, _Tp& __b)
noexcept(__and_<is_nothrow_move_constructible<_Tp>,
is_nothrow_move_assignable<_Tp>>::value)
{
_Tp __tmp = std::move(__a);
__a = std::move(__b);
__b = std::move(__tmp);
}
交换后,__tmp
(类型Foo
)拥有std::function<void()>
对象m_deleter
且没有目标。析构函数和析构函数调用m_deleter();
答案 2 :(得分:0)
即使使用Visual C ++ 2013,您也可以重现您的问题,而{C} 2013会执行not support defaulted move constructors and assignment operators;自编函数也会出现相同的行为:
#include <functional>
class Foo {
public:
Foo(int) : m_deleter{ []() {} } {}
Foo(const Foo &) = delete;
Foo(Foo &&src) : m_deleter(std::move(src.m_deleter)) { };
Foo & operator=(const Foo &) = delete;
Foo & operator=(Foo &&src) { m_deleter = std::move(src.m_deleter); return *this; }
~Foo()
{
m_deleter();
}
private:
std::function<void()> m_deleter;
};
int main() {
Foo foo(1);
Foo bar(2);
std::swap(foo, bar);
}
然后,您可以使用Visual Studio中的调试器来验证发生了什么。在std::swap
来电时设置一个断点。您将最终进入函数的VC实现:
_Ty _Tmp = _Move(_Left);
_Left = _Move(_Right);
_Right = _Move(_Tmp);
所有这三个动作都能正常工作。但是函数的范围结束了,_Tmp
变量的生命周期也结束了。当m_deleter
为空时,将在其上调用析构函数,您可以在调试器GUI的“Locals”部分中看到:
移动意味着移动的对象必须保持有效状态以进行销毁,并且导致调用空std::function
的状态无效。其他人已经向你展示了析构函数中的修复程序。
现在关于这个...
事实证明它与Visual Studio 2015和GCC 4.9.2崩溃,但没有 与Clang 3.5。
你的原始代码和我的修改都崩溃了Clang 3.5:
terminate called after throwing an instance of 'std::bad_function_call'
what(): bad_function_call
bash: line 7: 25250 Aborted (core dumped) ./a.out
我在http://coliru.stacked-crooked.com/处尝试过,根据clang++ --version
使用clang version 3.5.0 (tags/RELEASE_350/final 217394)
。