However, though versions of this text appear all over on the internet my question is not completely answered by this explanation. Though it may be implied, since global variables always seem to be static it would be nice to have a straight out answer.
If you didn't explicitly create it, you don't need to explicitly destroy it.
Let's just remove some of the words from your standard quote:
Static: Objects declared in global or namespace scope (§6.3.4) ... are created and initialized once (only) and ‘‘live’’ until the program terminates (§15.4.3). Such objects are called static objects.
So, if you declare an object with global or namespace scope, it is static, and its lifetime is roughly the program lifetime.
By analogy, consider normally-scoped local variables:
void foo() {
std::string s;
// code
}
now there is no way to destroy s
without exiting its enclosing scope. If you explicitly destroy it in-place, the destructor will still be called again when the scope exits.
Now, consider global and namespace scope to be the top-level scope of your program. You enter this scope before main
(which is nested inside the global scope) and exit it after leaving main
. This top-level scope lives exactly as long as the program, and you're no more able to destroy the its objects early than you are objects in any other scope.
There is some subtlety about exactly when the constructor gets called (generally before main starts, with a fudge to allow dynamically-loaded libraries without having to talk about platform specifics), when the destructor gets called (relative to exit handlers, say), and the relative ordering of these for different statics (not well-defined, so don't make global initialization or destruction depend on other globals).
Of course, if you want the object to be global for convenience, you can still choose to have explicit setup/cleanup calls instead of using the ctor/dtor for this.
In most plugin systems in C and C++, the loading of plugins works like this:
The main application specifies a path where the plugins should be located (or allows such a path to be configured). On startup, the application looks for either dynamically loadable libraries (DLLs on Windows, .so on Linux, in case of compiled plugins) or scripts (in case of interpreted/scripted plugins) in the plugin path.
For each plugin that it finds, the application tries to call an init/registration function that allows the plugin to register itself with the application. For interpreted/scripted plugins, this could be the main body of the script.
To support interpreted/scripted plugins, the application needs to incorporate a scripting engine that understands the language that the plugin is written in.
When writing a compiled plugin, one of the rules is that you build it as a dynamically loadable library and that you provide the initialization function with the exact name and signature that the main application expects.
Furthermore, any classes that the plugin provides must be derived from an interface specified by the main application for plugins that provide a certain service.
For a scripted plugin, the requirement is that the initialization function performs the initializations and registrations that are expected by the main application.
Best Answer
Passing a pointer to a state object around is probably the best solution. Why?
The statefulness of your functions is made explicit and obvious. It is the most general and most extensible solution.
Unfortunately, APIs with a (hidden) global state are fairly common. They tend to make simple things simpler, but often make more difficult things outright impossible.
E.g. imagine a database client library. To access a database, you need to create a connection first. What happens if I want to connect to multiple databases at the same time? If there is a single global connection hidden in the library, this is outright impossible.
Passing an extra parameter around is somewhat annoying. But as long as all the state is grouped into a single object that has to be carried around, it isn't very annoying.
Even if I would decide to keep a hidden global state, I would group that state into a single object so that it becomes easier to manage. Keeping track of multiple related global variables is quite error-prone, checking whether a single global variable has been initialized is much easier. Note that with a hidden global state, you will have to check whether the state is set in each exported function in order to prevent user errors.