【问题标题】:Is it possible to catch an exception of lambda type?是否可以捕获 lambda 类型的异常?
【发布时间】:2019-05-06 20:28:21
【问题描述】:

虽然只抛出派生自std::exception 类的类型的异常是一种好习惯,但C++ 可以抛出任何东西。以下所有示例都是有效的 C++:

throw "foo";  // throws an instance of const char*
throw 5;      // throws an instance of int

struct {} anon;
throw anon;   // throws an instance of not-named structure

throw []{};   // throws a lambda!

最后一个例子很有趣,因为它可能允许传递一些代码以在捕获站点执行,而无需定义单独的类或函数。

但是是否有可能捕获 lambda(或闭包)? catch ([]{} e) 不起作用。

【问题讨论】:

  • 我想我们可以假设 catch(...){} 是不想要的。
  • catch (...) { asm("call %rax"); } 不安全的问题需要不安全的解决方案
  • @Joshua catch(...) 仅是因为不授予对被捕获的异常对象的访问权限。
  • @sudo rm -rf slash:我认为你没有尝试过。你把 x64 调用约定弄错了。
  • @Joshua 是的,我没有尝试过。这更像是一个笑话,而不是一个严肃的想法。我也不认为有任何东西迫使编译器使用 rax。

标签: c++ exception lambda


【解决方案1】:

lambda 是一种唯一的匿名类型。命名 lambda 实例类型的唯一方法是将其存储在变量中,然后对该变量类型执行 decltype

有几种方法可以捕获抛出的 lambda。

try  {
  throw []{};
} catch(...) {
}

在这种情况下,你不能使用它,只能再次扔掉它。

try  {
  throw +[]{};
} catch(void(*f)()) {
}

可以将无状态的 lambda 转换为函数指针。

try  {
  throw std::function<void()>([]{});
} catch(std::function<void()> f) {
}

您可以将其转换为std::functionstd::function 的缺点是它为更大的 lambda 分配了堆内存,这在理论上可能会导致它抛出。

我们可以消除堆分配:

template<class Sig>
struct callable;

template<class R, class...Args>
struct callable<R(Args...)> {
  void* state = nullptr;
  R(*action)(void*, Args&&...) = nullptr;
  R operator()(Args...args) const {
    return action( state, std::forward<Args>(args)... );
  }
};

template<class Sig, class F>
struct lambda_wrapper;
template<class R, class...Args, class F>
struct lambda_wrapper<R(Args...), F>
:
  F,
  callable<R(Args...)>
{
  lambda_wrapper( F fin ):
    F(std::move(fin)),
    callable<R(Args...)>{
      static_cast<F*>(this),
      [](void* self, Args&&...args)->R {
        return static_cast<R>( (*static_cast<F*>(self))( std::forward<Args>(args)... ) );
      }
    }
  {}
  lambda_wrapper(lambda_wrapper && o):
    F(static_cast<F&&>(o)),
    callable<R(Args...)>( o )
  {
    this->state = static_cast<F*>(this);
  }
  lambda_wrapper& operator=(lambda_wrapper && o)
  {
    static_cast<F&>(*this) = (static_cast<F&&>(o));
    static_cast<callable<R(Args...)>&>(*this) = static_cast<callable<R(Args...)>&>( o );
    this->state = static_cast<F*>(this);
  }
};

template<class Sig, class F>
lambda_wrapper<Sig, F> wrap_lambda( F fin ) {
  return std::move(fin);
}

现在你可以这样做了:

try {
  throw wrap_lambda<void()>([]{});
} catch( callable<void()> const& f ) {
}

callable 是比std::function 更“轻量级”的擦除类型,因为它不会导致分配新的堆内存。

Live example.

【讨论】:

  • @krzy 你错过了+;我从来没有含蓄地说。 throw +[]{};
【解决方案2】:

C++ 允许你抛出任何东西。它可以让你抓住你扔的任何东西。当然,你可以抛出一个 lambda。唯一的问题是,要捕获某些东西,您需要知道该东西的类型或至少是父类型。由于 lambda 不是从公共基础派生的,因此您必须知道 lambda 的类型才能捕获 lambda。主要问题是每个 lambda 表达式都会给你一个distinct type 的右值。这意味着你的 throw 和你的 catch 都需要基于相同的 lambda 表达式(注意:相同的表达式,而不仅仅是一些看起来完全相同的表达式)。在某种程度上,我能想到的一种方法是将 lambda 的创建封装到一个函数中。这样,您可以在 throw 表达式中调用该函数,并使用函数的返回类型将类型推断为 catch

#include <utility>

auto makeMyLambda(int some_arg)
{
    return [some_arg](int another_arg){ return some_arg + another_arg; };
}

void f()
{
    throw makeMyLambda(42);
}

int main()
{
    try
    {
        f();
    }
    catch (const decltype(makeMyLambda(std::declval<int>()))& l)
    {
        return l(23);
    }
}

试试看here

您也可以像其他一些答案中建议的那样使用std::function,这可能是一种更实用的方法。然而,这样做的缺点是

  • 这意味着您实际上并没有抛出 lambda。你抛出一个std::function,这不是你真正要求的?
  • 从 lambda can throw an exception 创建 std::function 对象

【讨论】:

    【解决方案3】:

    异常处理程序基于类型进行匹配,并且为将异常对象匹配到处理程序而进行的隐式转换比在其他上下文中受到更多限制。

    每个 lambda 表达式都引入了对周围作用域唯一的闭包类型。所以你天真的尝试是行不通的,因为 []{} 在 throw 表达式和处理程序中有一个完全不同的类型

    但你是对的。 C++ 允许你抛出任何对象。因此,如果您事先将 lambda 显式转换为与异常处理程序匹配的类型,它将允许您调用该任意可调用对象。例如:

    try {
        throw std::function<void()>{ []{} }; // Note the explicit conversion
    } catch(std::function<void()> const& f) {
        f();
    }
    

    这可能有有趣的实用性,但我会警告不要扔不是来自std::exception 的东西。更好的选择可能是创建一个派生自 std::exception 的类型并且可以保存可调用对象。

    【讨论】:

    • 当然,我不会在生产代码中使用它。相反,我正在探索语言的复杂性。虽然我确实尝试通过指向函数的指针来捕获,但我并没有想到以 std::function 的形式抛出和捕获。
    • @KrzysiekKarbowiak - 如果精心设计的类型,我不明白为什么你不能在生产中做到这一点。正如我所指出的,它可能具有有趣的实用性。毕竟,独创性是采用已知的方法并以新颖的方式使用它:)
    • this answer 中提供了用于匹配 catch 表达式的确切规则。可悲的是,如果 lambda 被抛出为throw []{};,看起来没有办法捕获它,因为没有公共基类并且类型不是指针,所以指针规则不适用。
    【解决方案4】:

    你可以扔和接std::function

    #include <iostream>
    #include <functional>
    
    void f() {
            throw std::function<void(void)>([]{std::cout << "lambda\n"; });
    }
    
    int main()
    {
            try{ f(); }
            catch( std::function<void(void)> &e)
            {
                    e();
                    std::cout << "catch\n";
            }
    }
    

    输出:

    lambda
    catch
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-14
      相关资源
      最近更新 更多