是否可以捕获lambda类型的异常?

时间:2018-12-05 09:51:23

标签: c++ exception lambda

虽然最好只抛出从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)不起作用。

4 个答案:

答案 0 :(得分:50)

根据类型对异常处理程序进行匹配,并且将异常对象与处理程序进行匹配所进行的隐式转换比在其他上下文中受到的限制更大。

每个lambda表达式都会引入一个闭包类型,该闭包类型对于周围的作用域是唯一的。因此,您的幼稚尝试将不起作用,因为[]{}在throw表达式和处理程序中具有完全不同的类型

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

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

这可能有一个有趣的实用程序,但是我要当心不要抛出不是从std::exception派生的东西。更好的选择可能是创建一个从std::exception派生并可以容纳可调用类型的类型。

答案 1 :(得分:23)

C ++允许您抛出任何东西。而且它允许您捕捉所扔的任何东西。您当然可以抛出一个lambda。唯一的问题是,要捕获某些内容,您需要知道该内容的类型或至少父类型。由于Lambda并非来自共同的基础,因此您必须知道Lambda的类型才能捕获Lambda。这样做的主要问题是,每个lambda表达式都会为您提供distinct type的右值。这意味着您的投掷和接球都必须基于相同的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对象

答案 2 :(得分:6)

您可以扔并抓住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

答案 3 :(得分: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