Consider a custom error type written using the LLVM system_category implementation for reference:
#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;
}
Now imagine the following simple class that uses std::error_code to handle errors:
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;
}
};
The following code gives alarming output
static MyObj obj;
int main() {
obj.method(); // remove this line, and the output is fine
}
name:1
Destroyed the category
Running cleanup
name:2
Note how my_error_category_type::message was called on a destructed object!
My questions are:
- Is calling
messageon this destructed object safe? - If not, is there a way to preserve the lifetime of the category? Can I make the object immortal somehow?
- Does the standard make any guarantees about the lifetime of the builtin
std::system_category()objects and the like? The LLVM implementation I link to above suffers exactly the same problem.
It is not safe to call object methods after its destructor is called.
The question is how to control objects order of destruction. Statics are destructed in reverse order of initialization, so my_error_category_type will be destructed before MyObj, because its constructor is called after MyObj constructor. It is not a problem, that needs to be solved by standard, but rather architectural problem.
So, we gotta somehow control the destruction order. The simplest way is to ensure that
objdestructor is called earlier:Program output:
Now
MyObjdestructor is called earlier, not after main, but afterF()end, becauseMyObjis a scope variable, and it's destructed afterF()finish andstatic my_error_category_type cis destructed when main finishes.But if we still wanna make MyObj static, there is such technique called Nifty Counter Idiom, that helps to destroy statics only after last use. But it has its tradeoffs. link
Similar problem with statics: "static initialization order fiasco" (link).