在 Visual Studio 中调用 std::swap 时的 std::bad_function_call
Posted
技术标签:
【中文标题】在 Visual Studio 中调用 std::swap 时的 std::bad_function_call【英文标题】:std::bad_function_call when calling std::swap in visual studio 【发布时间】:2015-03-09 08:00:39 【问题描述】:我正在尝试将我的代码从 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 中没有。
【问题讨论】:
@Ron Tang:如果我将其设为默认值,它仍然会崩溃。 en... , 使用我的 linux gcc 也有同样的错误! 在 linux 中它可以完美运行。您使用哪个编译器? @Ron Tang:如我的问题所述,我使用 Clang 3.5。 在 ubuntu 上使用 gcc 4.9.1 崩溃,出现相同的异常 【参考方案1】:简介
这种行为的原因很简单; m_deleter
在Foo
的析构函数中被无条件调用,即使在它不可调用的情况下也是如此。
std::swap
的直接实现创建了一个临时对象来保存两个操作数之一的中间结果,这个临时对象没有可调用的 m_deleter
。
什么是std::bad_function_call?如果您尝试调用@,将抛出std::bad_function_call 987654322@ 没有要调用的有效目标。
细化
我们可以将您的 testcase 简化为以下更明确的 sn-p:
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
已被移出在 14 行;它不再可调用并引发异常。
解决方案
~A::A ()
if (_cb) // check if callable
_cb ();
【讨论】:
所以 Clang 的一个 bug 不会崩溃? @gartenriese - 不是 Clang 错误。标准只要求被移动的对象可以正常释放,至于是否保持原值不需要。 @gartenriese No,实现不能使被 moved-from 的对象无效 - 只是说对象必须在“有效但未指定的状态”。这反过来意味着,当使用 libc++(*clang 附带的标准库的实现)时,您将获得对其中一个回调的额外调用..但这不是实现错误(如果任何东西都是你的)。 我明白了。好吧,您的解决方案很简单,并且适用于所有三个编译器,所以谢谢 :-) @FilipRoséen-refp - 我发现你的代码有漂亮的行号,你是怎么做到的?请告诉我,谢谢。【参考方案2】:std::swap()
中使用了一个临时对象。当swap()
返回时,临时对象的m_deleter
为空。当临时销毁时,m_deleter();
会抛出 std::bad_function_call
,因为 m_deleter
没有目标。
我机器(gcc4.9.1,ubuntu)上的std::swap
如下:
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();
【讨论】:
【参考方案3】:即使使用 not support defaulted move constructors and assignment operators 的 Visual C++ 2013,您也可以重现您的问题;自写函数也会发生同样的行为:
#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)
。
【讨论】:
我用的是clang 3.5.0,它没有崩溃。 gartenriese:如果你用调试器单步执行它并且异常离开析构函数,它会做什么? 什么意思?使用 Clang 我没有例外。 @gartenriese:那么你的空std::function
到底会发生什么?
我没有可用的调试,抱歉。它只是继续程序,就好像 std::function 不为空一样。以上是关于在 Visual Studio 中调用 std::swap 时的 std::bad_function_call的主要内容,如果未能解决你的问题,请参考以下文章
Visual Studio 调试器:轻松查看 std::list(和其他 std 容器)
无法在 Visual Studio 2019 和 2022 中使用 std::counting_semaphore
Visual Studio 中的 std::packaged_task 错误?