No, not in the face of any sort of concurrency it doesn't...
I fail to see how does that relate to the article in question.
The author provided an example for which counter() works well, he didn't claim that the same "pattern" would be good for 100% of the use cases.
static int prv_counter;
int counter() { return ++prv_counter; }
Is insanity. Like, this is programmer malpractice. This function can only ever be called from one thread (not to mention: using `int` instead of `int64_t` is also a trivial mistake, this easily overflows). This is the kind of thing that enters a code base, works fine for long enough that everyone forgets about it, then causes horrible security issues and crashes. The idea of saying this is "good use" of a global variable is... this person should not be giving advice on good coding.If you want to do this (and you shouldn't, because global state is bad for 14 other reasons), at the very least, make it thread safe and not trivially overflowing:
static std::atomic<int64_t> prv_counter { 0 };
int64_t counter() { return 1 + prv_counter.fetch_add(1, std::memory_order_relaxed); }
(the 1+ is because the original author used pre-increment instead of post-increment)Like, this is not awesome, and you shouldn't do it, but it's at least not a total disaster.
EDIT: actually, this is also bad, because the `prv_counter` is not really private at all. The better way to do that would be:
int64_t counter() {
static std::atomic<int64_t> prv_counter { 0 };
return 1 + prv_counter.fetch_add(1, std::memory_order_relaxed);
}
Three different serious issues in two lines of code, fun!Constant global variables can be very useful. Mutable global variables can be totally fine in a singlethreaded (e.g. typical embedded) program. I agree that you should still use them with caution, but every advice that says: "don't do $X" should come with instructions under which circumstances it is valid and under which it isn't — and how to get the intended behavior instead with e.g. message queues, locks or whatnot.
In a well designed code base, only a very small part of the code should need to worry about concurrency and parallelism.
Best case scenario is that the loads and stores are interleaved, which leads to multiple threads returning the same value when calling counter(), which will guarantee crashes elsewhere in the program (the purpose of functions like these is to produce UNIQUE values, after all). But it's undefined behaviour in any case, it's just unacceptable to put in a C/C++ code base.
That is completely untrue.
No, please don't
Your program must be really small and scoped for this to make sense.
Also, kudos for providing so many corner cases to avoid that are non-trivial to formulate.
It feels like one of those "it's not impossible to do right" cases.
I'll just cite one evaluation point from the post:
> It's extremely easy to use incorrectly.
> Your program must be really small and scoped for this to make sense.
to me suggests that it's not really global state if your program has to stay small and scoped. In a sense, your program has just become the context boundary for the state, instead of a function, or class, or database.
I realise that this line of argument effectively leads to the idea that no state is global, but perhaps that gives us a better way to understand the claim that 'global variables can work', which they undoubtedly can. It's fine for a program (or a thread, as in the original article) to be the context which bounds a variable's scope.
I’m sorry but no. Humans are human and mistakes will be made. I’ve lost count of the number of esoteric bugs I’ve had to track down due to global state being changed and not put back properly.
If you have to qualify a pattern with rules that are easily forgotten or open to corner case bugs, it’s far better to just not use that pattern in the first place.
If your narrow the usage of a global within a file you can get a lot of mileage. That's not how people tend to use globals and why I wanted to write about it
1. It should be hard or impossible to use incorrectly. For example, counter() keeps increments consistent. 2. If you change observable state, restore it.
That can be summarised as "To prevent mistakes: Don't make any mistakes". It made lots of sense once I saw this was by a C++ programmer, C++ is the language with, as a prominent C++ practitioner put it: False Positives for the question: Is this a valid program?
If you're used to a language which gaslights you by having the compiler not emit any diagnostics whatsoever and just calmly handing you a nonsensical output executable because what you wrote was subtly wrong obviously global variables seem fine, what's not to like? You just have to be inhumanly competent at all times, which was the baseline requirement for the entire language.
I think it’s somewhat similar to unchecked memory access. Used correctly it works just fine and offers extremely high performance. Unfortunately, history has shown that over enough time mistakes will be made. As an industry we’re now to the point of actively denouncing entire languages over the risks of unchecked memory access.
Using software development patterns that rely on the engineers to follow a bunch of rules to do things correctly will eventually burn you. Better to avoid them entirely if at all possible.
Immutable global state is fine to be accessible from the whole code base.
Pure functions are best of course, but you can't build real-world things entirely from pure functions.
The problematic approach lives somewhere inbetween, passing context/object references into deep callstacks isn't really a good solution for anything.
That's just plain wrong. There's even a language built entirely around pure functions: Haskell.
Not going to use it in anything I get paid for though, lol. At one point there was a commonly accepted "singleton" pattern for things like logging but that's just globals with extra steps. In Go I just create services in my main and pass them around where needed.
This is what you should do if you must use globals, but it doesn't really give any advantages.
I find the append_work especially egregious. Never ever use a bloody global for that! Goodness gracious me. What an absolute and utter waste that provides zero utility. If you have a job system just pass a damn handle to the job queue. Good lord.
If everyone spent a little effort to not use globals the world would be a much better place. The value add of globals when they are mildly useful is so inconsequential such that it's basically never worth the effort.
However this just underlines again why globals are usually good to avoid. It takes rigor not to make mistakes that can completely mess up with program state.
I also strongly disagree with the benefits author advances for having global. Code feels actually less spaghetti when things are properly scoped and not accessible from everywhere, which hides extremely well dependencies and makes it way harder to reason about the code.
One famous exception to that is indeed logging (which in itself is based on global state when printing to stdout or stderr anyway).
And also
> on account of how few places your object can be stored to
you can store things in a single place, just once, without the need for globals.
Most languages make global and static variables thread-global by default, and making them thread-local is more work. I can see why, but that piece of language design causes a lot of global variable problems.
Also: you can simplify a lot of problems by deciding that something is going to be limited to n=1, whether that's variables or threads, and then a business reason comes along where you really want to have n=2. Suddenly every global is a source of regret.
We stick to these kinds of rules because most people are not great programmers all the time. It's just mostly better to do the safe and boring thing most of the time.
let's = let us, it is for suggestions
lets = I let, he-she-it lets, it's the verb form