在 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_deleterFoo 的析构函数中被无条件调用,即使在它不可调用的情况下也是如此。

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

交换后,__tmpFoo 类型)持有一个没有目标的std::function&lt;void()&gt; 对象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 容器)

std库中的Visual Studio编译错误

无法在 Visual Studio 2019 和 2022 中使用 std::counting_semaphore

Visual Studio 中的 std::packaged_task 错误?

无法在 Visual Studio 2015 中将 typedef 转换为 std::pair

在 MS Visual Studio 2013 中,我可以使用啥来代替 std::aligned_alloc?