4

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.

gartenriese
  • 4,131
  • 6
  • 36
  • 60

3 Answers3

3

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 (); 
}
Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • So it's a bug with Clang to not crash? – gartenriese Mar 09 '15 at 09:10
  • @gartenriese - not Clang bug.The standard requires only that the object being moved can normally be released, as to whether to hold the original value is not required. – Ron Tang Mar 09 '15 at 09:17
  • @gartenriese **No**, an implementation must not invalidate the object being *moved-from* - all that is said is that the object must be in a "*valid but unspecified state*". This in turns mean that when using *libc++* (the implementation of the standard library that ships with *clang), you will get an extra invocation of one of the callbacks.. but it's not an implementation bug (if anything it's yours). – Filip Roséen - refp Mar 09 '15 at 09:19
  • I see. Well, your solution is easy and works on all three compilers, so thanks :-) – gartenriese Mar 09 '15 at 09:22
  • @FilipRoséen-refp - I found that your code has pretty line number, how did you that? Please tell me, thanks. – Ron Tang Mar 09 '15 at 09:41
  • @RonTang: I upvoted this answer, but the line numbers are a very bad idea, because they break that whole copy-&-paste-into-my-editor thing. – Christian Hackl Mar 09 '15 at 09:45
  • @ChristianHackl I normally don't do *line-numbering*, in this case I thought I'd make more references to individual line-numbers in the explanation of the snippet - something which didn't happen. I agree that one should, normally, not include line-numbers in their post. – Filip Roséen - refp Mar 09 '15 at 09:48
  • @RonTang I'm a terminal user; `cat -n $FILE` or whatever floats your boat does the trick. – Filip Roséen - refp Mar 09 '15 at 09:49
2

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

fefe
  • 3,342
  • 2
  • 23
  • 45
0

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 debugger showing <code>std::swap</code> call with temporary variable whose destructor will cause a crash

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).

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62