在静态析构函数中如何安全地使用`std :: error_category`?

时间:2019-03-01 07:44:05

标签: c++ c++11 language-lawyer system-error

考虑使用LLVM system_category实现编写的自定义错误类型,以供参考:

#include <iostream>
#include <system_error>

struct my_error_category_type : std::error_category {
    char const* name() const  noexcept override { return "name"; }
    std::string message(int i) const noexcept override{ return "message"; }
    ~my_error_category_type() {
        std::cout << "Destroyed the category" << std::endl;
    }
};

std::error_category const& my_error_category() noexcept {
    static my_error_category_type c;
    return c;
}

现在想象一下下面的简单类,它使用std::error_code处理错误:

std::error_code do_some_setup() {
    return std::error_code(1, my_error_category());
}
std::error_code do_some_cleanup() {
    return std::error_code(2, my_error_category());
}

struct MyObj {
    void method() {
        // this constructs the category for the first time
        auto err = do_some_setup();
        std::cout << err << std::endl;
    }

    ~MyObj() {
        std::cout << "Running cleanup" << std::endl;
        auto err = do_some_cleanup();
        std::cout << err << std::endl;
    }
};

以下代码提供警报输出

static MyObj obj;

int main() {
    obj.method();  // remove this line, and the output is fine
}
name:1
Destroyed the category
Running cleanup
name:2

请注意如何在已破坏的对象上调用my_error_category_type::message

我的问题是:

  1. 在此销毁的对象上调用message是否安全?
  2. 如果没有,是否可以保留类别的生存期?我能以某种方式使物体永生吗?
  3. 该标准是否对内置std::system_category()对象等的寿命提供任何保证?我上面链接到的LLVM实现遇到了完全相同的问题。

2 个答案:

答案 0 :(得分:3)

调用对象方法的析构函数后,调用它是不安全的。

问题是如何控制对象的破坏顺序。静态是以相反的初始化顺序破坏的,因此my_error_category_type将在MyObj之前被破坏,因为其构造函数是在MyObj构造函数之后调用的。这不是问题,需要通过标准解决,而是架构问题。

因此,我们必须以某种方式控制销毁顺序。 最简单的方法是确保提前调用obj析构函数:

void F() {
  MyObj obj;
  obj.method(); 
}

int main() {
  F();
}

程序输出:

name:1
Running cleanup
name:2
Destroyed the category

现在MyObj的析构函数被更早地调用,而不是在main之后,而是在F()结束后被调用,因为MyObj是作用域变量,并且在F()完成并{ {1}}在main完成时被破坏。

但是,如果我们仍然想使MyObj成为静态对象,则有一种称为 Nifty Counter Idiom 的技术,该技术仅在上次使用后才有助于消除静电。但它有其权衡。 link

与静态相似的问题:“静态初始化顺序惨败”(link)。

答案 1 :(得分:-1)

这似乎是std::system_category实施中的错误。

作为一种变通方法,MyObj构造函数可以调用std::system_category / my_error_category,以便在构造MyObj之前构造函数静态错误类别,因此只能将其销毁在MyObj被销毁之后:

MyObj::MyObj() noexcept { static_cast<void>(my_error_category()); }