在阅读clickhouse代码的过程中,发现有一种函数定义形式很怪异,一度以为是代码写错了。。。


int main() 
try
{
    XXX
}
catch (DB::Exception & e)
{
    XXX
}

形式如上,函数名之后怎么能不加花括号呢?这是不是不符合语法?深入了解之后发现原来是我孤陋寡闻了。

https://en.cppreference.com/w/cpp/language/function-try-block

上面的代码其实跟下面差不多

int main() 
{
    try
    {
        xxx
    }
    catch (DB::Exception & e)
    {
        xxx
    }
}

第一种看起来比较怪异的形式在cpp里叫做function-try-block, 第二种就是我们常见的function-body

那么function-try-block主要使用于哪种场合呢?它是不是能捕获到所有的异常呢?让我们看下文档

The primary purpose of function-try-blocks is to respond to an exception thrown from the member initializer list in a constructor by logging and rethrowing, modifying the exception object and rethrowing, throwing a different exception instead, or terminating the program. They are rarely used with destructors or with regular functions.

Function-try-block does not catch the exceptions thrown by the copy/move constructors and the destructors of the function parameters passed by value: those exceptions are thrown in context of the caller.

Likewise, function-try-block of the main() function does not catch the exceptions thrown from the constructors and destructors of static objects (except for the constructors of function-local statics).

function-try-block这种形式主要用于类构造函数和main函数中,而很少使用在类析构函数和其他常规函数中。

  • 在类的构造函数中,function-try-block能够捕获到初始化列表中的异常,用户可以在catch block中打印日志、修改异常、重抛异常。需要注意的是,function-try-block无法捕获传值参数的复制/移动构造函数/析构函数中抛出的异常,这些异常会传递到function-try-block调用者的上下文中。
  • 在main函数中,function-try-block能够捕获try块中的异常,但是无法捕获静态对象构造和析构过程中抛出的异常

相关实例如下所示:

#include <exception>
#include <iostream>

class MyException : public std::exception 
{
public:
    MyException(const char* msg) : msg(msg) { }
    virtual const char* what() const throw() { return msg.c_str(); }

private:
    std::string msg;
};

class M
{
public:
    explicit M(size_t size)
    {
        throw MyException("exception from constructor");
    }

    M(const M & other)
    {
        throw MyException("exception from copy constructor");
    }

    M(M && other)
    {
        throw MyException("exception from move constructor");
    }
    
    ~M()
    {
        // throw MyException("exception from destructor");
    }
};

class A
{
public:
    explicit A() try: 
            m1(10), 
            m2(m1), 
            m3(std::move(m2))
    {
    }
    catch (std::exception & e)
    {
        std::cout << "catch exception in A:A: " << e.what() << std::endl;
        throw;
    }

    ~A() = default;
private:
    M m1;
    M m2;
    M m3;
};

int main()
try
{
    A a;
}
catch (std::exception & e)
{
    std::cout << "catch exception in main: " << e.what() << std::endl;
}

执行初始化列表m1(10)的时候会抛出异常,然后被A::A中的try catch捕捉到,重新抛出到main函数的上下文,接着被main中的try catch捕捉到。因此运行结果为

catch exception in A:A: exception from constructor
catch exception in main: exception from constructor