3

I am coming from the Google C++ guidelines, specifically the section on Static and Global variables. It advises against having global variables of types that aren't trivially destructible. I'm unsure if file-scope global variables fall in this category. Specifically, I have an example of this in a small C++ file (.cpp, not .h):

namespace {
    static shared_ptr<Foo> sFoo;
}

where Foo has a virtual destructor. No reference or a copy of the shared_ptr is being passed out of the file and is solely utilized within the file. I've gone through multiple questions like this, and this. Most, as expected, indicate that they aren't as evil as the extern globals since their scope is limited(with which I concur to a great extent) but the Google guidelines especially advises against such uses (e.g. std::string, etc.). What are the possible pitfalls of having such instances as file-scope global variables, esp. for types like shared_ptr that aren't trivially destructible?

2 Answers 2

3

The problem with static/global variables is their storage duration, not just their scope/visibility/linkage. In particular, it is impossible to predict the order in which multiple global variables with static storage duration are initialized and destroyed. I have thought multiple times that I'd be able to work around issues with static initialization/destruction, but a number of segfaults have convinced me otherwise.

The life cycle of a variable with static storage duration is the following:

  • the variable begins existence when the program is loaded
  • at some indeterminate point, initialization runs
  • now, the variable can be used
  • at some indeterminate point, the destructor is invoked
  • the program terminates

All of these issues apply regardless of whether the variable is declared static so that it is local to a compilation unit, though function-scoped static variables at least have predictable initialization (when the declaration is first executed).

The Google C++ style guide attempts to eliminate sources of possible errors. Then, the variable is legal almost during the entire run time of the program.

  • If variables with static storage duration are used, they should be initialized with a constexpr so that the variable is initialized immediately. Dynamic initialization is only allowed if this initialization does not depend on the ordering relative to other variables. Dynamic initialization for function-scoped static variables is usually OK.
  • The variables should use a trivial destructor (a destructor that does “nothing”) so that ordering is not a concern, and the variable effectively stays alive until the program terminates. Note also that a program might terminate without running these destructors, so you cannot rely on this destructor for correctness.

A shared_ptr cannot be used in accordance with these requirements.

  • Initialization is actually fine because the constructor is constexpr (the variable is initialized to a null pointer).
  • However, a shared_ptr is not trivially destructible so that there are ordering issues during the program's destruction phase.

A consequence of a static shared_ptr<Foo> could be that a Foo implementation has a destructor that runs at an unpredictable time, in particular at a time where other parts of your data are no longer alive. You mention that this shared pointer will only be used within that compilation unit, which means you could prevent such cases from happening and can audit any involved code with modest effort. In particular, if Foo's destructor is trivial, then the shared_ptr might be safe in practice. But since the safety of this variable depends on lots of other code, it is error-prone. The Google C++ style guide instead discourages this potentially dangerous pattern entirely.

I'd also recommend against using mutable static variables since they're inherently not threadsafe, unless protected with atomics or a mutex. While a multiple shared_ptr instances managing the same object can be used concurrently, concurrent access to the same shared_ptr object/variable is only allowed with additional synchronization.

-1

There are several problems. One, if you have static variables, then you likely have code that relies on their existence. And at some point close to the end of the program, the variable changes first to “being destructed” and then to “not existing”. Does the rest of your code survive this? Is it tested?

Two, there’s no guarantee that the destructor runs. Programs crash. You’d have to look very carefully at the life cycle of a mobile app, I wouldn’t bet that these destructors are getting called at all.

Third, in 2022 lots of code is run asynchronously. Normally this is no problem, such code will eventually finish. But here, the program stops, quite possibly in the middle of some background operation.

I’d say you cannot rely on the destructor being executed, important if it does things that affect persistence (like flushing caches and writing them to files). And if there is no persistent effect, why bother executing the destructor at all?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.