简体   繁体   中英

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

I am trying to port my code from linux to windows. However with Visual Studio my code crashes with the following error:

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

This is my code:

#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);
}

It crashes when I use std::swap . In linux it worked flawlessly.

Weirdly enough, when I try to compile it online via GCC it doesn't work either. What am I doing wrong and why does at work at home with Clang (3.5).

EDIT: It turns out it crashes with Visual Studio 2015 and GCC 4.9.2, but not with Clang 3.5.

Introduction

The reason for the behavior is quite simple; m_deleter is unconditionally invoked in the destructor of Foo , even in circumstances when it isn't callable.

The straight forward implementation of std::swap creates a temporary to hold the intermediate result of one of the two operands, this temporary will not have a callable m_deleter .


What is std::bad_function_call ?
std::bad_function_call will be thrown if you try to call a std::function which doesn't have a valid target to invoke.



Elaboration

We can reduce your testcase to the following, more explicit, snippet:

 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 } 

The Problem

When leaving swap temporary will be destroyed, which in turn will try to invoke _cb - the problem is that temporary._cb has been moved-from on line 14 ; it is no longer callable and an exception is thrown.


The Solution

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

A temporary object is used in std::swap() . When swap() returns, the temporary object's m_deleter is empty. When the temporary destructs, the m_deleter(); throws std::bad_function_call as m_deleter has no target.

The std::swap on my machine (gcc4.9.1, ubuntu) is like the following:

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);
  }

After swap, __tmp (of type Foo ) holds a std::function<void()> object m_deleter with no target. The exception is thrown when it destructs and the destructor calls m_deleter();

You can reproduce your problem even with Visual C++ 2013, which does not support defaulted move constructors and assignment operators ; the same behaviour occurs with self-written functions:

#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);
}

You can then use the debugger in Visual Studio to verify what's going on. Put a breakpoint at your std::swap call. You will end up in the VC implementation of the function:

_Ty _Tmp = _Move(_Left);
_Left = _Move(_Right);
_Right = _Move(_Tmp);

All three of these moves will work correctly. But then the scope of the function ends, and so does the lifetime of the _Tmp variable. The destructor will be called on it while its m_deleter is empty, as you can see in the "Locals" section of the debugger GUI:

Visual Studio调试器显示<code> std :: swap </ code>调用临时变量,其析构函数将导致崩溃

Moving means that the moved-from object has to remain in a valid state for destruction, and a state which results in calling an empty std::function is not valid. Others have shown you the fix in the destructor already.

Now about this...

It turns out it crashes with Visual Studio 2015 and GCC 4.9.2, but not with Clang 3.5.

Both your original code and my modification crash with 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

I tried it at http://coliru.stacked-crooked.com/ , which according to clang++ --version uses clang version 3.5.0 (tags/RELEASE_350/final 217394) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM