std::bad_function_call 在 visual studio 中调用 std::swap 时

std::bad_function_call when calling std::swap in visual studio

我正在尝试将我的代码从 linux 移植到 windows。但是 Visual Studio 我的代码崩溃并出现以下错误:

Microsoft C++ exception: std::bad_function_call at memory location

这是我的代码:

#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 中没有。

std::swap()中使用了一个临时对象。当swap()returns时,临时对象的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);
  }

交换后,__tmpFoo 类型)持有一个没有目标的 std::function<void()> 对象 m_deleter。析构时抛出异常,析构函数调用m_deleter();

简介

该行为的原因很简单; m_deleterFoo 的析构函数中被无条件调用,即使在它不可调用的情况下也是如此。

std::swap 的直接实现创建了一个临时文件来保存两个操作数之一的中间结果,这个临时文件不会有可调用的 m_deleter


什么是std::bad_function_call?
std::bad_function_call will be thrown if you try to call a 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 } 

问题

当离开 swaptemporary 将被销毁,这反过来会尝试调用 _cb - 问题是 temporary._cb 有在 14 移出;它不再可调用并抛出异常。


解决方案

~A::A () {
  if (_cb) // check if callable
    _cb (); 
}

即使使用 Visual 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 的状态无效。其他人已经向您展示了析构函数中的修复。

现在关于这个...

It turns out it crashes with Visual Studio 2015 and GCC 4.9.2, but not with 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)