【问题标题】:Why do I get "exception spec is more lax than base" error with this piece of code?为什么这段代码会出现“异常规范比基础更宽松”错误?
【发布时间】:2014-12-27 16:29:08
【问题描述】:

尝试使用 Xcode 6.1 中的 clang 版本(基于 LLVM 3.5svn 的 clang-600.0.54)编译以下代码,使用 -std=c++11-stdlib=libc++ 给我一些我不太明白的错误。

#include <functional>

struct Impl
{
    typedef std::function<void ()> L;
    L l;
    int i;
};

struct Hndl
{
    Impl* impl;
    Hndl(Impl* i): impl(i) {}
    ~Hndl() noexcept(false) {}
};

int main(int argc, char * argv[]) {
    Hndl h(new Impl());
    h.impl->l = [=]
    {
        h.impl->i = 42;
    };
    return 0;
}

结果:

In file included from t.cpp:1:
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1293:52: error: exception specification of overriding
      function is more lax than base version
template<class _FD, class _Alloc, class _FB> class __func;
                                                   ^
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1593:13: note: in instantiation of template class
      'std::__1::__function::__func<<lambda at t.cpp:20:14>, std::__1::allocator<<lambda at t.cpp:20:14> >, void ()>' requested here
        if (sizeof(_FF) <= sizeof(__buf_) && is_nothrow_copy_constructible<_Fp>::value)
            ^
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1697:5: note: in instantiation of function template
      specialization 'std::__1::function<void ()>::function<<lambda at t.cpp:20:14> >' requested here
    function(_VSTD::forward<_Fp>(__f)).swap(*this);
    ^
t.cpp:20:12: note: in instantiation of function template specialization 'std::__1::function<void ()>::operator=<<lambda at t.cpp:20:14> >' requested here
        h.impl->l = [=]
                  ^
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1281:39: note: overridden virtual function is here
    _LIBCPP_INLINE_VISIBILITY virtual ~__base() {}
                                      ^
1 error generated.

看起来Impl::L::~L() 以某种方式继承了Hndl::~Hndl()noexcept(false),但我不知道为什么。 有趣的是,如果我在 lambda 中注释掉对 h.impl-&gt;i 的赋值,同样的代码也会编译。 如果我从Hndl::~Hndl() 中删除noexcept(false) 规范,也会编译,但我需要它(解释原因可能有点长,但我愿意)。 如果 lambda 通过 ref 捕获,也会编译,但这里的重点是能够复制共享实现的句柄。 将noexcept(true) 添加到Impl::~Impl() 没有帮助。

ideone.com 的 c++11 编译器很高兴地按原样编译它。

谁能解释一下这里发生了什么?

【问题讨论】:

    标签: c++ c++11 lambda libc++ noexcept


    【解决方案1】:

    lambda 捕获h,它有一个可能抛出的析构函数,因此 lambda 的闭包类型也有一个可能抛出的析构函数。

    考虑到这一点,您可以将问题简化为:

    #include <functional>
    
    struct F
    {
        void operator()() { }
    
        ~F() noexcept(false) {}
    };
    
    int main() {
        std::function<void ()> f = F{};
    }
    

    似乎 libc++ 中的 std::function 无法存储没有 nothrow 析构函数的可调用类型,这看起来像 libc++ 中的一个错误。

    从错误消息看来,修复可能就像在 __func 析构函数中添加显式 noexcept 一样简单,但我不熟悉实现,所以可能没那么容易。

    除了使用 noexcept 析构函数将 Hndl 类型包装在另一种类型中之外,我没有看到任何明显的解决方法,因此在 lambda 中捕获它不会使 lambda 具有 noexcept(false) 析构函数。我尝试过这样的事情,但是 libc++ 在shared_ptr 中似乎有类似的问题:

        std::function<void ()> f = std::bind(&Hndl::operator(), std::make_shared<Hndl>());
    

    【讨论】:

    • 这在标准中是否明确允许? This 参考声明 ~functionnoexpect 说明符,但它可能是错误的(再次)。
    • std::function 肯定有一个 noexcept 析构函数,但它仍然应该能够存储没有的类型。如果您的析构函数实际上没有抛出,则没有问题,程序应该可以完美运行。如果您的析构函数在存储在 std::function 中时抛出异常,则会导致调用 std::terminate()
    • @imre 因此,如果析构函数无论如何都抛出,代码不能保证工作。我看不出有理由宣布它noexcept(false)
    • @frymode,我不同意,它可能会在某些情况下抛出,但在其他情况下不会,所以noexcept(false) 是合适的。如果您碰巧知道它在存储在std::function 中时永远不会抛出,那么noexcept(false) 也是安全的。
    • 同意,这是有道理的。
    【解决方案2】:

    我认为问题在于 Hndl 在 lambda 中按值捕获,而 std::function 析构函数被视为 noexcept(true),因为它的定义中没有另外说明。所以Hndl 实例不能在l 析构函数中安全地销毁。至于如果从 lambda 中删除成员分配,为什么错误会消失 - 很可能捕获的值只是被编译器优化掉了。

    【讨论】:

    • 你的答案前半句是对的,最后一句是错的。如果您删除分配,错误就会消失,因为 h 没有被捕获,因此 ~Hndl() 的异常规范没有相关性。
    【解决方案3】:

    这是一个可能的解决方法,基于http://www.codeproject.com/Articles/313312/Cplusplus-Lambda-Storage-Without-libcplusplus

    请注意,以下代码为演示目的而进行了简化;它是不完整的,只处理 void() lambdas。请参阅上面的链接以了解真实情况(尽管在我的特定情况下 void() 就足够了)。

    #include <functional>
    
    struct Lambda
    {
        void* lambda;
        void (*execute)(void*);
    
        template <typename T> Lambda& operator=(T&& t)
        {
            lambda = new T(t);
            execute = [](void* lambda) { ((T*)lambda)->operator()(); };
            return *this;
        }
    
        void operator()() { execute(lambda); }
    };
    
    //  ---
    
    struct Impl
    {
        Lambda l;
        int i;
    };
    
    struct Hndl
    {
        Impl* impl;
        Hndl(Impl* i): impl(i) {}
        ~Hndl() noexcept(false) {}
    };
    
    int main(int argc, char * argv[]) {
        Hndl h(new Impl());
        h.impl->l = [=]
        {
            h.impl->i = 42;
        };
    
        h.impl->l();
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-19
      • 2020-07-26
      • 2011-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-28
      相关资源
      最近更新 更多