30

I recently came across the Nifty Counter Idiom. My understanding is that this is used to implement globals in the standard library like cout, cerr, etc. Since the experts have chosen it, I assume that it's a very strong technique.

I'm trying to understand what the advantage is over using something more like a Meyer Singleton.

For example, one could just have, in a header file:

inline Stream& getStream() { static Stream s; return s; }
static Stream& stream = getStream();

The advantage is you don't have to worry about reference counting, or placement new, or having two classes, i.e. the code is much simpler. Since it's not done this way, I'm sure there's a reason:

  1. Is this not guaranteed to have a single global object across shared and static libraries? It seems like the ODR should guarantee that there can only be one static variable.
  2. Is there some kind of performance cost? It seems like in both my code and the Nifty Counter, you are following one reference to get to the object.
  3. Is there some situations where the reference counting is actually useful? It seems like it will still just lead to the object being constructed if the header is included, and destroyed at program end, like the Meyer Singleton.
  4. Does the answer involve dlopen'ing something manually? I don't have too much experience with that.

Edit: I was prompted to write the following bit of code while reading Yakk's answer, I add it to the original question as a quick demo. It's a very minimal example that shows how using the Meyer Singleton + a global reference leads to initialization before main: http://coliru.stacked-crooked.com/a/a7f0c8f33ba42b7f.

Nir Friedman
  • 15,634
  • 2
  • 33
  • 63
  • 1
    You mean `static Stream s;`? – Barry Apr 25 '16 at 15:15
  • You have dynamic initialization, which can introduce order issues. The nifty counter is initialized to zero, [which happens before everything else](http://stackoverflow.com/a/5622848/597607). – Bo Persson Apr 25 '16 at 15:16
  • did you mean to make that local stream static? right now you're returning a reference to a local, wouldn't using it be dangerous? – dgel Apr 25 '16 at 15:16
  • @MSalters Well, as-is... we're returning a reference to a temporary... – Barry Apr 25 '16 at 15:17
  • @MSalters, wouldn't there only be 1 stream instantiated for the whole program? – dgel Apr 25 '16 at 15:20
  • @Barry: I see, the other stream - of course. I'll fix it. – MSalters Apr 25 '16 at 15:20
  • @Barry Thanks for catching that, pretty critical mistake, of course that's what I meant. – Nir Friedman Apr 25 '16 at 15:21
  • Compare [LWG 890](http://cplusplus.github.io/LWG/lwg-defects.html#890). Note also that this idiom is developed in C++98's time, before magic statics. – T.C. Apr 25 '16 at 16:00
  • @T.C. The "before magic statics" makes sense. The link you provided is highly informative, although it doesn't seem to do a direct comparison with the nifty counter AFAICS; from what I can tell it basically seems to be discussing whether or not the static global static should exist or just the function. Did I miss something? – Nir Friedman Apr 25 '16 at 16:19

4 Answers4

11

The static local/Meyer's singleton + static global reference (your solution) is nearly equivalent to the nifty counter.

The differences are as follows:

  1. No .cpp file is required in your solution.

  2. Technically the static Steam& exists in every compilation unit; the object being referred to does not. As there is no way to detect this in the current version of C++, under as-if this goes away. But some implementations might actually create that reference instead of eliding it.

  3. Someone could call getStream() prior to the static Stream& being created; this would cause difficulty in destruction order (with the stream being destroyed later than expected). This can be avoided by making that against the rules.

  4. The standard is mandated to make creating the static Stream local in the inline getStream thread safe. Detecting that this is not going to happen is challenging for the compiler, so some redundant thread-safety overhead may exist in your solution. The nifty counter does not support thread safety explicitly; this is considered safe as it runs at static initialization time, prior to when threads are expected.

  5. The call to getStream() must occur in each and every compilation unit. Only if it is proven that it cannot do anything may it be optimized out, which is difficult. The nifty counter has a similar cost, but the operation may or may not be be simpler to optimize out or in runtime cost. (Determining this will require inspecting resulting assembly output on a variety of compilers)

  6. "magic statics" (statics locals without race conditions) where introduced in C++11. There could be other issues prior to C++11 magic statics with your code; the only one I can think of is someone calling getStream() directly in another thread during static initialization, which (as mentioned above) should be banned in general.

  7. Outside the realm of the standard, your version will automatically and magically create a new singleton in each dynamicly linked chunk of code (DLL, .so, etc). The nifty counter will only create the singleton in the cpp file. This may give the library writer tighter control over accidentally spawning new singletons; they can stick it into the dynamic library, instead of spawning duplicates.

Avoiding having more than one singleton is sometimes important.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • 1
    Sorry about the race condition - I fixed the question which makes the first part of your question obsolete.. – MSalters Apr 25 '16 at 15:21
  • @NirFriedman rewrote answer nearly top to bottom. I mistook nifty counter for a different technique; my apologies. – Yakk - Adam Nevraumont Apr 25 '16 at 15:34
  • @Yakk Everything you said makes perfect sense, except for the static local not working. In my example, the header file declares a static global `stream`; this is a global so it has to be initialized before main. And in order to be initialized, it will call getStream. So getStream will be called before main, so the static local will be initialized before main. What am I missing? – Nir Friedman Apr 25 '16 at 15:40
  • @Yakk Cool, I think this is as good as it gets. I will note that some of the differences could be resolved by moving code around; e.g. I *could* have declared my reference extern in the header, and just put the rest of the code in the .cpp file. This eliminates 1., 2., 3 (getStream could be hidden in the .cpp file), and 5. So I'm not sure if these are quite fundamental differences. But they apply to my answer as written, and the rest of your points are correct. Personally, my take-away is that I will not be using the nifty counter in my code anytime soon. – Nir Friedman Apr 25 '16 at 17:46
  • @Yakk Also, I'm not 100% sure about #7. Because the singleton is created by calling a function, if a .so is loaded and the singleton is already present, that function should already be defined. So when the .so calls `getStream`, it will actually call the version that's already been defined and called, so a second singleton will not be created. Are you completely sure? – Nir Friedman Apr 25 '16 at 18:00
  • The actual main difference and reason for the nifty counter pattern is the shared lib behavior (listed 7 in @Yakk answer). – Amir Kirsh May 24 '16 at 09:38
  • When a shared library calls the `getStream` it compiles and links on the shared library as a call to its version of getStream with its own static, resulting with a separate singleton for the library. Since the streams library wants to avoid this, the nifty counter is necessary, in which the static counter is in the cpp. When dlls and shared objects are not present, Meyer's singleton should probably be the choice, being much easier to write and maintain. – Amir Kirsh May 24 '16 at 09:46
  • @AmirKirsh The solution I gave can be modified by only declaring (rather than defining) the function w/ static local in the header file. Then, the function is defined in a .cpp file which gets compiled into a .so. In this case, users of the singleton link against the .so. Each translation unit gets its own copy of the reference, but each reference is guaranteed to be initialized by calling the exact same copy of the function (which exists only once in the .so). – Nir Friedman May 24 '16 at 12:39
  • @AmirKirsh Even in the original solution, it is actually not that clear whether there will be multiple singletons. If a symbol in a library is weakly defined, then that symbol will look for a definition already present before using the library's version. getStream is a symbol, so if getStream is already defined in an executable, and the translation unit loads a .so/dll which weakly defines getStream, than the so/dll will use the executable's definition for the call to getStream, which will not create a new singleton. – Nir Friedman May 24 '16 at 12:42
  • @AmirKirsh Sorry for the spam, last bit: actually now that I think about it, getStream can in fact be marked as a weak symbol using a compiler pragma. This should actually completely solve the issue without needing to put it in a .cpp file or worry about a .so. I plan to test it at some point; if you have a test case that shows multiple singletons being created with this scheme, I'd love to see it. – Nir Friedman May 24 '16 at 12:53
  • It will not work with a static reference depending on an external module (probably wouldn't compile). It should compile with a static pointer, but then the pointer would be first initialized to 0 with undefined order of dynamic initialization, as there is no guarantee about order of dynamic initialization between translation units. See: http://stackoverflow.com/questions/7074647/is-lazy-initialization-really-possible-with-static-data-members and section 3.6.2 in the spec: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf – Amir Kirsh May 24 '16 at 12:54
  • @amir zero initialization of globals happens prior to dynamic initialization. C++11 magic statics provide other guarantees that are quite robust. I cannot speak to the quirks of dunamic library symbol loading and exporting, as they vary based on system and compiler. – Yakk - Adam Nevraumont May 24 '16 at 17:47
  • 2
    @Yakk I experimented with this recently as I'm planning to write a blog post. It turns out that this does not (on Linux at least) result in multiple copies of the singleton, even when used in .so's. This is because when a shared library is loaded, it's definition of a function (`getStream`) will only get loaded if it's not already defined. Thus, the global reference will actually use the existing definition of the function, with its existing static local. – Nir Friedman Oct 12 '16 at 18:09
  • @NirFriedman On linux, can you load a symbol explicitly from a specific `.so` by (mangled) name or offset? How about inlining ; wouldn't `getStream` be inlined into the calling code? Or is it the static *within* `getStream` that is not getting loaded as it was defined elsewhere? – Yakk - Adam Nevraumont Oct 12 '16 at 19:01
  • @Yakk Loading explicitly: I assume you are referring to dlopen and such? I think once you start loading things explicitly there are probably many ways to break many things; I didn't consider this kind of robustness a design goal but it's a good point. I had the same concern about inlining but it seems like static locals are scoped to the function `getStream::s`, so even if inlined it will still look for the function's definition. I'm going to test this carefully though. If you are interested and want to leave an email address, I can drop you a line when the blog post is published. – Nir Friedman Oct 12 '16 at 19:09
4

Summarizing the answers and comments:

Let's compare 3 different options for a library, wishing to present a global Singleton, as a variable or via a getter function:

Option 1 - the nifty counter pattern, allowing the use of a global variable that is:

  • assured to be created once
  • assured to be created before the first usage
  • assured to be created once across all shared objects that are dynamically linked with the library creating this global variable.

Option 2 - the Meyers singleton pattern with a reference variable (as presented in the question):

  • assured to be created once
  • assured to be created before the first usage

However, it will create a copy of the singleton object in shared objects, even if all shared objects and the main are linked dynamically with the library. This is because the Singleton reference variable is declared static in a header file and must have its initialization ready at compile time wherever it is used, including in shared objects, during compile time, before meeting the program they will be loaded to.


Option 3 - the Meyers singleton pattern without a reference variable (calling a getter for retrieving the Singleton object):

  • assured to be created once
  • assured to be created before the first usage
  • assured to be created once across all shared objects that are dynamically linked with the library creating this Singleton.

However, in this option there is no global variable nor inline call, each call for retrieving the Singleton is a function call (that can be cached on the caller side).

This option would look like:

// libA .h
struct A {
    A();
};

A& getA();

// some other header
A global_a2 = getA();

// main
int main() {
    std::cerr << "main\n";
}

// libA .cpp - need to be dynamically linked! (same as libstdc++ is...)
// thus the below shall be created only once in the process
A& getA() {
    static A a;
    return a;
} 

A::A() { std::cerr << "construct A\n"; }
Amir Kirsh
  • 8,021
  • 22
  • 43
  • Be careful on Microsoft platforms. Things may not work as expected. There's a reason [Microsoft waited nearly a decade](https://msdn.microsoft.com/en-us/library/hh567368.aspx) to implement C++11's core library feature of [Dynamic Initialization and Destruction with Concurrency](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm) – jww Mar 24 '17 at 00:21
1

All of your questions about utility / performance of Nifty Counter aka Schwartz Counter were basically answered by Maxim Egorushkin in this answer (but see also the comment threads).

Global variables in modern C++

The main issue is that there is a trade-off taking place. When you use Nifty Counter your program startup time is a bit slower (in large projects), since all these counters have to run before anything can happen. That doesn't happen in Meyer's singleton.

However, in the Meyer's singleton, every time you want to access the global object, you have to check if it's null, or, the compiler emits code that checks if the static variable was already constructed before any access is attempted. In the Nifty Counter, you have your pointer already and you just fire away, since you can assume the init happened at startup time.

So, Nifty Counter vs. Meyer's singleton is basically a trade-off between program startup time and run-time.

Community
  • 1
  • 1
Chris Beck
  • 14,006
  • 3
  • 43
  • 79
  • I don't think this is correct; in the code that I show above you access the singleton through the `stream` reference, not by calling the function `getStream`. `stream` is a simple reference that has already been initialized, control flow does not pass through `getStream` when accessing `stream`, so there's no reason why there would be a check of the guard variable to see whether the static local is already initialized. – Nir Friedman Apr 25 '16 at 17:23
  • @Chris The meyer's singleton is only invoked once, at startup, in the OP's code. So the "every time you want to access the global object, you have to check" does not apply. – Yakk - Adam Nevraumont Apr 25 '16 at 17:26
  • @NirFriedman: I guess you are right -- when you do it that way, you seem to get the best of both worlds. *hmm* – Chris Beck Apr 25 '16 at 17:33
  • @ChrisBeck Yes, I'm very confused. I actually thought that Icy's answer was correct, but surprisingly C++ makes pretty stringent guarantees about static locals in the same translation unit. This is quite a pickle. – Nir Friedman Apr 25 '16 at 17:40
0

With the solution you have here, the global stream variable gets assigned at some point during static initialization, but it is unspecified when. Therefore the use of stream from other compilation units during static initialization may not work. Nifty counter is a way to guarantee that a global (e.g. std::cout) is usable even during static initialization.

#include <iostream>

struct use_std_out_in_ctor
{
    use_std_out_in_ctor()
    {
        // std::cout guaranteed to be initialized even if this
        // ctor runs during static initialization
        std::cout << "Hello world" << std::endl;
    }
};

use_std_out_in_ctor global; // causes ctor to run during static initialization

int main()
{
    std::cout << "Did it print Hello world?" << std::endl;
}
mpm
  • 1,046
  • 9
  • 22
  • The global `stream` is in the header. To use `stream`, you must #include the header, which means that in your translation unit, the static global `stream` shows up before any code you write, which means that `stream` is guaranteed to be initialized before you use it in any static objects. – Nir Friedman Apr 28 '16 at 16:11