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

ilmyapht  于 2022-11-19  发布在  其他
关注(0)|答案(5)|浏览(123)

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

更新(2022年11月14日):

参见here我自己的答案,考虑到C++20的特性。

umuewwlo

umuewwlo1#

异常处理程序是基于类型进行匹配的,并且为将异常对象与处理程序匹配而进行的隐式转换比在其他上下文中受到更多限制。
每个lambda表达式都引入了一个闭包类型,这个闭包类型对于周围的作用域是唯一的。所以你的天真尝试是行不通的,因为[]{}在throw表达式和句柄中有一个 * 完全不同的类型 *!
但是你是对的。C++允许你抛出任何对象。所以如果你事先显式地将lambda转换成一个与异常处理程序匹配的类型,它将允许你调用那个任意的可调用对象。例如:

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

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

im9ewurl

im9ewurl2#

C++允许你抛出任何东西。它允许你捕捉你抛出的任何东西。当然,你可以抛出一个lambda。唯一的问题是,要捕捉某个东西,你需要知道它的类型,或者至少知道它的父类型。由于lambda不是从公共基派生的,你必须知道你的lambda的类型才能捕捉lambda。2主要的问题是每个lambda表达式都会给予你一个不同类型的右值。这意味着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);
    }
}

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

  • 这意味着你实际上并没有抛出lambda,而是抛出了一个std::function,这并不是你真正想要的😉
  • 从lambda创建std::function对象会引发异常
wpcxdonn

wpcxdonn3#

您可以投掷和接住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
svujldwt

svujldwt4#

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

jhdbpxl9

jhdbpxl95#

现在是2022年,当前的C标准是C20,它给了我们一些更有趣的机会,这是我在看了这个演讲后意识到的:C++ Lambda Idioms,尤其是this part
Lambda现在是默认可构造的,并且允许在未赋值的上下文中使用(例如decltype),这允许我们这样做:

#include <iostream>

using lambda = decltype([]{ std::cout << "I am a lambda\n"; });

auto foo()
{
    throw lambda();
}

int main()
{
    try {
        foo();
    } catch (const lambda& l) {
        l();
    }
}

Try it out on Compiler Explorer
因此,目前看起来简短的答案是是的,可以捕获lambda类型的异常

相关问题