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

时间:2015-03-09 08:00:39

标签: c++ visual-studio gcc clang

我正在尝试将我的代码从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没有崩溃。

3 个答案:

答案 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”部分中看到:

Visual Studio debugger showing <code>std::swap</code> call with temporary variable whose destructor will cause a crash

移动意味着移动的对象必须保持有效状态以进行销毁,并且导致调用空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)