Why I Don't Like Singletons

  Рет қаралды 94,114

The Cherno

The Cherno

Күн бұрын

Пікірлер: 441
@TheCherno
@TheCherno 3 ай бұрын
What’s your opinion on this? Leave more comments for me to make videos about 👇 Also don’t forget you can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
@michaplucinski142
@michaplucinski142 3 ай бұрын
Hello! This video definietly made what you meant more clear, however, I am not sure how other way of implementing system like this could look like. For me that's because I can't imagine resource manager class having many instances to kind of use well what class are made for (creating objectS (or at least that's what I think ) ), but at the same time I feel like it makes sense for it to be a class, so that it has it's own data storage, it's own methods and all of that fancy thing which classes provide us with. But then it is singleton again, right? So if you could make a video explaining that or pointing me (and anyone reading this who has similar problem) to some reliable information source or maybe even just answering here how it could actually look like, I would be really grateful =).
@gweltazlemartret6760
@gweltazlemartret6760 3 ай бұрын
Hi! At 24:16 you say a static global is not available in C#, but I swear it is. You can define your singleton as another’s class static variable, likely in the Program body `static SingletonClass mySingletonStatic = new ()` should work to handle a(nother) magical var usable everywhere in this class' scope, at the condition the SingletonClass itself isn’t static. This static ref of a pure class is likely something Java could handle aswell. It’s not the exact same top-level file defined static variable, but it does sound close enough. (`static readonly` in C# prevents the reference change case you mention afterwards.)
@gweltazlemartret6760
@gweltazlemartret6760 3 ай бұрын
For the last point, you should go for data-class, this could alleviate the need for a "one class does all everywhere" need: place only the data you need in the datatype you need, and let manager classes manage the workload on this data.
@rinket7779
@rinket7779 3 ай бұрын
There is an advantage to the static inside the method, it's guaranteed to be thread safe when it's initialized.
@guywithknife
@guywithknife 2 ай бұрын
@@michaplucinski142 just because you can’t imagine a specific class having more than one instance, doesn’t mean you should enforce that it can never have more than one instance. A singleton is a bad choice when you only “want” one instance, because it ENFORCES it. Instead it should be used only when it’s important that you only have one instance (and global access) - if you don’t require both of those, then it’s probably not the right tool. For example, I’ve often heard people give a logger as an example saying they would never want more than one, so make it a singleton! Until you do want more than one: maybe you want different types of logs to go to different places (screen vs file vs to the gui vs network), maybe you want an audit log, maybe user facing log and internal log, whatever. In a game it’s less likely that you need multiple loggers than in business software, but again, why constrain needlessly? You cannot know the future need. So for a resource manager, sure, you probably only need one, but there’s no reason to force that limitation. What if you have an editor build of your engine (editor and engine in one) and you want to have a separate resource manager for each? Why might you want this? Perhaps because you want to keep the game runtime as similar to a real scenario as possible (eg to track memory and access) so don’t want editor-only resources in the same one. Is it a likely scenario? Maybe not, but it’s just to illustrate that just because you can’t imagine a case for it doesn’t mean none exists. Instead, create a normal class, then instantiate it only however many times you need. Done. As for global access: if you need it global, then make a global. If you don’t, then pass it in eg by reference to the dependents that need it.
@derivepi6930
@derivepi6930 3 ай бұрын
Get married and become a Coupleton. Even better, a Factory.
@Kokurorokuko
@Kokurorokuko 3 ай бұрын
My girlfriend is abstract
@felipeliradev
@felipeliradev 3 ай бұрын
@@Kokurorokuko💀
@soniablanche5672
@soniablanche5672 3 ай бұрын
wife becomes a BabyFactory
@spiderpig20
@spiderpig20 3 ай бұрын
My girlfriend is virtual
@JM-is1vf
@JM-is1vf 3 ай бұрын
Delete the copy constructor of the Wife class to avoid UB.
@codemetas1284
@codemetas1284 3 ай бұрын
Wow! I did not expect my tiny comments on the other video to blow up and trigger such an extensive response. Let me begin this reply by reiterating the most important part of those comments: I am sorry if I came across as rude or condescending, that was not my intention at all! I merely saw claims I deemed factually wrong or at least misleading without further explanation, which I considered worth pointing out. Judging by the upvotes and this video, it seems like you and others agreed :) I also agree that discussion is a good way to grow as a community and as individuals! Just to make this abundantly clear once more: I really am not a fan of singletons and don't want to be upheld as their champion xD I'd much prefer something like the "tree" you describe any day of the week. I have no major objections to anything stated in this video, just one minor nitpick: The moment you move the static pointer from the function into your class and add the "if(!s_Instance)" check to initialize on first use you lose the guaranteed thread safe initialization of the "standard" Meyers Singleton and would have to manually add locking or similar to preserve it. This does - of course - become irrelevant once you change your code to use explicit initialization, which you presumably will only ever call once from one defined place and before any potentially accessing other threads are started.
@kolosso305
@kolosso305 3 ай бұрын
Congrats on being featured lol
@aloluk
@aloluk 3 ай бұрын
Yeah, i'm waiting for any mention of thread safety, part way through video so far.
@patrickgono6043
@patrickgono6043 3 ай бұрын
Regarding lazy loading: I remember playing Albion Online. The game was available on mobile and PC. Every time a spell or ability was used for the first time, PC users would get a lag spike. Especially on devices with HDDs. An SSD improved the situation somewhat, but the problem was still there. The lazy loading meant that all the textures, shaders, sounds etc. for the abilities had to be read from file, taking much, much longer than ~16 ms (i.e. running at 60 fps). Though I understand why they went this way, as mobile devices have fast storage but usually are quite limited in memory. Still, would have been nice to have the flexibility to preload everything on PC while lazy-loading on mobile.
@JDoucette
@JDoucette Ай бұрын
A game I released on the Xbox 360 using XNA and the XACT sound system would have a frame lag on the first projectile fired. I couldn't get into the sound system to control the loading and caching as I wanted, so I coerced it to stop lazy loading by playing the sounds at volume 0 to force them to load. (The game was Score Rush, a bullet hell, small hit box, tight controls, one hit death... so the gameplay couldn't survive any frame lags.)
@GuilhermeTeres
@GuilhermeTeres 3 ай бұрын
You had a good point in the video. I'm a senior game engine dev and also the guy behind Cave Engine (that Ive been written for almost ten years and currently have around 130k loc) and I would like to throw my two cents here on how I ended up figuring out the same solutions you suggested in the video. For that, let me tell a small story that happened to me. When I started writing engines years ago, it was after all the software engineer stuff we learn at university so I obviously tried to over engineer it a bunch. I found singletons very interesting at first and started using them all over the place. The first problem I had, which is probably the most obvious is the dependency thing that you mentioned in the video. it gets really hard to know and handle when a specific Singleton gets in each realized or destroyed if they rely on other singletons. And what is interesting about this is that this problem is very easily spotted by anyone writing your system like that if they use/develop the software for even a small amount of time. so when I see someone with this singleton madness, the first thing that comes to my mind is that the programmer does not have a lot of experience. So, moving on, as I evolved as a game engine programmer, I found that it's best to have up to one Singleton max, which is the app or application Singleton. The first iterations I did on that, more than 10 years ago, was exactly like the Dev from the previous video did: statically allocating it on the heap. But funny enough, just like the previous case, if you use the system or develop it for long enough, you very soon start to realize that this approach is also another rabbit hole. The main problem as you demonstrated in the video is exactly that you do not have control over when this get initialized or destroyed.. and that's a problem! So I'm sharing all this because I found very interesting while watching the video and remembering all that, because it is impressive how a lot of times the best way to learn and evolve as a programmer is the simply spend enough time, writing and dealing with your own code. Cave Engine nowadays, have exactly one singleton, which is the app, and it's implementation is very similar to the one you suggested with a couple more functionalities. A good one that I would love to point out here is a static method to check if the instance is a really initialized or not. Because some other systems in your code Could be made to also work regardless of the singleton being valid or not. And the thing is that when you have such a large codebase, specially, if you're not working alone, something that I ran into a lot in the past is deep down the cold, having systems being initialized in the singleton's constructor that also relies on the singleton. Causing an initialization loop that may not be trivial to deal with. Anyways , nice video!
@linnealager6146
@linnealager6146 3 ай бұрын
I am in the singleton craze phase right now. They are so cool and can make the code look really clean. I've used it for listener pattern, input manager, state stack and more. I've heard singletons are considered an antipattern but was never convinced why. Your comment was very insightful for me.
@ferinzz
@ferinzz 3 ай бұрын
As someone who started with programming robots for school I find it fascinating that people would skip initialization phases as that's the first thing you do. determine what needs to be done for everything to be in place and how to return the robot back to its station once it's all done. Then also make sure that it stops if/when it looses track of where it's at so that it doesn't fly into stuff and break it.
@shadichy
@shadichy 3 ай бұрын
As a fresher, I personally do not even bother to use more than one singleton in my code. I just feel like 1 is enough to "be a global variable manager" that can actually be initialized whenever the app starts. But now after I read your comment I feel like it makes sense. Very helpful!
@TheEVEInspiration
@TheEVEInspiration 2 ай бұрын
*All Applications really need is a good bootstrap, e.g. controlled initialization.* This requires so have a clear, well thought out architecture as to what becomes online, "when". Then we have a well established, predictable runtime-environment for the application code work with. And programmers are saved from always changing networks of context objects needing to be passed around, that at scale become meaningless, slow and tedious. It's setting up the run-time environment that matters. Never leave "main" or process input, before this most important job is done IMO!
@TheEVEInspiration
@TheEVEInspiration 2 ай бұрын
@@ferinzz I fully agree!
@JaceMorley
@JaceMorley 3 ай бұрын
My stance is that traditional global singletons and global variables are invisible parameters on every single function call in your application, and so greatly prefer explicit dependencies via dependency injection patterns. They do not make code cleaner, they just make the mess harder to see and deal with. EDIT: to clarify, DI does *not* mean using a DI framework. It's only the idea of passing in dependencies instead of instantiating them or pulling them from global state. Like 'polymorphic', it's a fancy name for a simple idea, and the frameworks/libraries are tools that can help reduce boilderplate but not needed for following the principle.
@Shadow-xi2sv
@Shadow-xi2sv 3 ай бұрын
++
@Kokurorokuko
@Kokurorokuko 3 ай бұрын
​@@calvinsomething5348Singletons are problematic because they use static methods. Static methods, in general, are a problem for testing because you have to be more careful not to create tests that affect other tests
@KingKarEl100
@KingKarEl100 3 ай бұрын
Also makes testing (unit,integration, take your pick) harder since every change to the singleton affects the outcome of the next test
@danielsharp2402
@danielsharp2402 3 ай бұрын
At the end of the day you either provide it as a parameter or it's going to be global that is the two options. Providing all dependencies as parameters is cumbersome as all hell so we cheat with globals. Fancy DI containers are also globals unless you pass them as parameter to everything again.
@JaceMorley
@JaceMorley 3 ай бұрын
@@danielsharp2402 Classes can contain references to their dependencies that are passed in via constructors, functions can take parameters too if needed. That way dependencies are transparent and documented by the code itself, and lifetimes are clear to the developer. With globals all that information is hidden and obscured, but that's just hiding important information for code maintainence. The DI containers really are just for automatically hooking up implementations to interfaces and managing lifetimes, but shouldn't be appearing in the actual 'business logic' code, and you can do all that just by initializing stuff before you pass it in if you like, they're not needed for following DI practices.
@alexkhazov7264
@alexkhazov7264 3 ай бұрын
On top of that, the System class doesnt need to be a class. It shoild be a namespace with free functions. All the state can be an internal struct in the translation unit
@CDBelfer4
@CDBelfer4 3 ай бұрын
You could make the argument that with static class functions which would behave essentially the same as free functions, you have the added benefit of having private/protected functions instead of the weird detail/impl inner namespace that is usually done
@garrafote18
@garrafote18 3 ай бұрын
@@CDBelfer4 from my experience singletons, static classes and system namespaces all achieve the same functionality. However I tend to prefer system namespaces because they don't polute header files with unnecessary private/implementation detail data leading to faster overall compile times for the pplication and most importantly faster compile times when iterating on the system - changing header files can sometimes be a real pain when working on big projects. So as long as you have control over the system's lifetime and don't polute headers with unnecessary data I wouldn't mind the chosen implementation pattern.
@theo-dr2dz
@theo-dr2dz 3 ай бұрын
First off: I am not a fancypants architect. I program in two contexts: at work, where I typically have no influence on the architecture, and at home where I do everything myself and have absolute power over everything and no team to worry about and all that stuff. So, you quite often have classes of which it doesn't make sense to have more than one of it. So, my solution would be to simply not create more than one of it instead of going out of your way to create clever ways to make it absolutely impossible to create a second one (even with multithreading, doing silly classloading or reflection tricks and all). Defend your singleton with the Nancy Reagan defence: just say no. Just don't do it. Making a resource manager dependent on game states (or layers as he calls it)? I typically would implement the paused state as a game state. If pausing the game would unload all resources, and unpausing would cause all resources to be reloaded, that would be not very logical. So you would have to create layers that do that and a different kind of layer that doesn't do that. Already makes stuff more complicated. Another one: if your main gameplay uses ECS, you would like your ECS manager classes alive from the moment actual gameplay starts up to the moment that it stops. So, you initialise all that when he exits the main menu to start the game proper. You don't want your ECS to disappear every time the player goes into the options menu screen and be recreated from scratch when he returns to the game. When he is in the options screen, the ECS stuff is not active, but it should remain alive. Only when he quits, you unload all that. What I would typically do is to have a kind of context class that holds unique pointers to all these central things that should be widely accessible and alive almost all the lifetime of the program, and pass that context around to every function that needs access. It is not completely global, just largely global. Simply let the context run out of scope whenever it is no longer needed, this will call the destructor and free everything. Also, I really don't like init() and shutdown() functions. It should be RAII if at all possible. It's way too easy to forget to call init(), call it twice, call shutdown() at the wrong point or twice and all that. I've seen the best programmers cause segmentation faults. It's too easy. Maybe at the point where the resource manager is created it is not yet possible to load textures. Add an add_texture() function to do that and a way to indicate that there are zero textures loaded. Seems much easier to me.
@oliver_twistor
@oliver_twistor 2 ай бұрын
I don't like init() either, because I like to know that when I have constructed an object, it is fully ready to use. In general, if one has methods they _must_ call every time, that functionality should probably be contained within the constructor.
@lavatheif
@lavatheif Ай бұрын
omg i learned programming 9 years ago from you, im so glad you still make videos
@jamesburgess9101
@jamesburgess9101 Ай бұрын
great post, thank you! Agree with most of your reasoning but Ooph, 21:33 look out! You just introduced a data race in your code. One of the super important things about a Meyer's singleton is the compiler inserts a lock before the initialization of a function local static assignment. Your new "if" needs an explicit lock if there is the chance of multiple threads calling this. Since the whole point is "this can be called from anywhere" that is also likely going to include "can be called from any thread". Anyway, I think worth mentioning, as EEVBlog Dave would say, definitely a trap for the younger players since the lock is not obvious.
@zombi1034
@zombi1034 3 ай бұрын
Because Java is mentioned many times in this video, I want to mention that the most idiomatic and error proof way to create a singleton in Java is by defining an Enum with a single constant (that being the singleton instance). It is thread safe, it guarantees that only a single instance can ever be created (including through tricks like serialization or reflection) and it provides lazy loading automatically. And you can define methods, implement interfaces just like you would on your normal singleton class.
@kostiapereguda
@kostiapereguda 3 ай бұрын
I don’t think it provides lazy loading in the same sense as here. Enum instance initialization happens during class loading (as does any static initialization), and while class loading itself is lazy, as it happens when you refer to the class for the first time, it is not the same as referring to the class instance for the first time. For example, you can trigger class loading (and thus creation of your instance) of a MySingleton enum by typing the statement “Class.forName(“MySingleton”)” anywhere
@kostiapereguda
@kostiapereguda 3 ай бұрын
But yeah, singletons though enums are cool
@emilyy-dev
@emilyy-dev 3 ай бұрын
​@@kostiapereguda loading a class doesn't trigger initialization, for example, doing Foo.class will load Foo but won't initialize it (this is easily testable by having a println in a static init block), however calling/accessing any static method/field or constructor will trigger initialization
@kostiapereguda
@kostiapereguda 3 ай бұрын
@@emilyy-dev you are right actually
@mr.m8539
@mr.m8539 3 ай бұрын
​@@kostiaperegudaJust wanted to you for being adult enough to admit being wrong. The world needs more people like you.
@suryanshusharma3227
@suryanshusharma3227 3 ай бұрын
Good take. Need more videos talking about design decisions.
@Spongman
@Spongman 3 ай бұрын
Ditch Init() & Shutdown(), just do that stuff in the constructor/destructor. Stack-allocate your singleton object ("System system;") & just use raii to control its lifetime. that ensures you can never get your lifetime boundaries overlapped, it's also exception-safe.
@madwax5803
@madwax5803 Ай бұрын
I was thinking the same thing while watching the video. There are two 'rules' I would add 1) The singleton object should be an interface or public API to a (sub?) system (think logging system with logError(...), logWarning(...)), NEVER use the global directly/indirectly internally in the "system" and do the hole pass ref thing, good for injection/test etc etc. 2) In the constructor Assert/throw/Jump up and down if the global is NOT nullptr, that stops a BIG problem.
@sebbbi2
@sebbbi2 2 ай бұрын
The classic Design Patterns book didn’t explain singleton destruction problem at all. In trivial cases these lazy static singleton just works, but once you start having dependencies the system will crash at shutdown, and crash is system and compiler specific as destruction order is not well defined. Simple example: You have two singletons: memory allocator and logger. System is shut down and language destroys all static objects. It decides to destroy the memory allocator first. Logger is destroyed next and in logger destructor it frees memory. But the memory allocator singleton static is already destroyed. Thus we get a crash at shut down. The only way to make singletons work in C/C++ is to have explicit shutdown calls and do them in right order. Once you have done this, you might as well do the init calls also explicitly in the right order to get nice symmetric and deterministic init and shutdown. This makes debugging and stepping easier when something fails during system init.
@sebbbi2
@sebbbi2 2 ай бұрын
If you dislike calling MyClass::getInstance().myFunc(), you can also have a public static pointer in the class called instance. Then calls look like this: MyClass::instance->myFunc(). This way programmer knows there’s no function call overhead. Compiler can of course inline these GetInstance() calls, but it’s not guaranteed to do that. And the instance pointer is simpler to reason about. Init and shutdown are of course the same in both ways.
@Slashx92
@Slashx92 Ай бұрын
That last part actually sounds quite nice if one must use singletons
@simspawn
@simspawn 10 күн бұрын
Absolutely love your videos. I dont even understand a fraction of what you're talking about, but its still enjoyable. There was even a moment when you talk about a first init and closing down and I was like "yes, thats exactly the kind of thing I want to avoid in the future..." but this is all still way over my head. Man I cant wait to actually be able to understand all this.
@dhickey5919
@dhickey5919 2 ай бұрын
Thank you, Cherno. Really enjoyed you diving into details on Singletons and how it's applied in architecture. The asserts video you mentioned would be great to see as well.
@Chukozy
@Chukozy 3 ай бұрын
While I can't speak for everyone, we need more of these videos sharing your insights and expertise!
@dct4890
@dct4890 3 ай бұрын
I really enjoyed this one. There's nothing better than multiple perspectives. I watch a lot, and they're all good, and I always learn something. Stay on the mend.
@Strazz801
@Strazz801 3 ай бұрын
What a lovely and in depth video! Thanks Cherno , super insightful for someone like me that’s finally starting to come to grips with all the complexity of the language.
@retakenroots
@retakenroots 3 ай бұрын
After 25+ years development experience I completely stopped using Singletons altogether but also developed a strong dislike for globals. Singletons are in a way just nicely wrapped globals. I only work (in my own projects) with dependency injection. It makes the code more clearer (shows intent) and testing a lot easier. Yes you get more boilerplate code and sometimes classes and objects that contain references that are not used by the instance itself but by one of its 'children' but for me that is acceptable
@prostmahlzeit
@prostmahlzeit 3 ай бұрын
That's true, but please add that dependency injection also provides the ability to export as singleton or export with scope. That way you still can have some classes created once or created once per subtree
@almightysapling
@almightysapling 3 ай бұрын
In my opinion Singletons are just *poorly* wrapped globals. Extra OOP fluff for no gain in safety/utility/readability.
@dagoberttrump9290
@dagoberttrump9290 2 ай бұрын
same. and DI ftw!
@justanaveragebalkan
@justanaveragebalkan Ай бұрын
If you have classes that are injected because of interfaces in the dependency provider, just lazy load them, i see no point in registering them as accessible dependencies if the only reason they are being registered for is to be consumed for other dependencies.
@retakenroots
@retakenroots Ай бұрын
@@justanaveragebalkan fair point but as injection typically happens at construction, the constructor signature will already show me all the required dependencies at that point. That said in really big projects this might not be what you want but for me it is an indicator that when I have a lot of injectables I might need to rethink the design.
@jlr3739
@jlr3739 3 ай бұрын
Using singletons for the property that they’re accessible anywhere is problematic for sure. But you can also get way too granular with systems/classes to the point where all work is done by dispatching calls to functions all over memory. Having a ‘Singleton’ or a manager that is in charge of a system and runs said system on all entities is not only simple, it’s also generally more performant and makes it clearer when something is happening. It’s an alternative to overly OO-ifying your code architecture. But yes, lazy initialization for an important system like this is ill advised.
@almightysapling
@almightysapling 3 ай бұрын
Regarding the lazy init being bad in this specific case: does it really matter? *Because* it's so critical it's certainly going to be one of the first things created. The "rocket launcher" example in the video makes sense but just doesn't apply to the memory manager.
@pharoah327
@pharoah327 2 ай бұрын
Am I missing something here? Lazy Initialization is independent of the Singleton design pattern. You can create the singleton when the application starts or create it when it is first grabbed. Either way works and it is easy to switch between the two. Having a singleton instance doesn't mandate either lazy initialization or greedy initialization. You get to choose which one suits your application better.
@JavedAlam-ce4mu
@JavedAlam-ce4mu Ай бұрын
@@pharoah327 They are referring to the specific example from the code review.
@funkdiscgolf
@funkdiscgolf 3 ай бұрын
Please make the logging/tracing/asserts you mentioned around 26:30 !!!! specifically how you go about the code changes between configurations. (preprocessor, oop , just branching, ...) , what different configs you setup/recommend/use? (like debug, release, final, internal, external, testing, ...). What build configs are the most and least useful in development? my drag and drop setup for any new project is a spdlog setup with some simple preprocessor flips between debug, release, and final configs. It works, but i often find it could be extended in a bunch of ways and would love to hear your thoughts and methods!
@duncangibson6277
@duncangibson6277 3 ай бұрын
Fantastic explanations of pros and cons of [variations of] the Singleton pattern, and alternate ways of achieving more control over timing and access 👍 More discussion of architecture issues like this please 👍
@ashleigh.
@ashleigh. 3 ай бұрын
tl;dw sorry, but something I noticed the C# peeps posting about but I'm not sure if it was addressed in the video, `AddSingleton` in C# DI is not a singleton that this video is talking about, rather that is a "singleton lifetime", a very important distinction
@kolosso305
@kolosso305 3 ай бұрын
I was literally just researching this topic for the past few days as I was refactoring my engine. I can tell this is going to be juicy!
@DynamicalisBlue
@DynamicalisBlue Ай бұрын
Asynchronous lazy loading is pretty much essential in modern games. Preloading a rocket asset on level load and hanging onto it for the entire level is not ideal when you can have thousands of high quality assets but only a few hundred ever present in your scene. It’s an insane amount of memory.
@3draven
@3draven 23 күн бұрын
If it's a game object that is constantly used like a projectile then you would preload rockets / bullets in an object pool and keep a set amount in memory and pull them into existence enabling their physics / needed raycasts / sound as needed and recycle them. Constant memory allocation / de-allocation is just bad practice.
@DynamicalisBlue
@DynamicalisBlue 23 күн бұрын
@ Async loading is not the same as constantly streaming stuff in and out. For something like a projectile, once it’s loaded in, you’d want to keep it in for quite some time as well as set pools up for them, like you mentioned, but likely not forever. There’s no reason why you can’t use async ‘on-demand’ loading but give the asset and pools an expiry time of let’s say, 10 minutes. This way, you don’t need to preload potentially hundreds or thousands of different assets related to weapons until someone actually uses said weapon. Once no one uses the weapon for 10 minutes, then it’s assets and pools.
@Splntxx
@Splntxx 3 ай бұрын
Really nice video quality - lighting and angle is perfect!
@flyinginthedark6188
@flyinginthedark6188 3 ай бұрын
The code suggested in the video is not exactly the same as what Meyers version gives you. The Meyers Singleton is thread safe, meaning it implicitly provides atomic check and a mutex during initialization, which allows you to do lazy init and access the instance from more than one thread. The version with global variable is good only if it doesn't do lazy init and has explicit Init() call as shown in the last example, or else you are risking data races in your program.
@Serendipity4477
@Serendipity4477 3 ай бұрын
On a modern compiler the static variable actually takes care of this automatically. But this also hides it from the developer, which is a really bad thing. I had several cases where callling this kind of singleton access function completely tanked performance highly multithreaded code and figuring that out is sometimes not that easy.
@rinket7779
@rinket7779 3 ай бұрын
What? No it doesn't, can you point to a source backing up your claim that any static is thread safe initialized? It only applies to statics defined inside functions
@flyinginthedark6188
@flyinginthedark6188 3 ай бұрын
@@rinket7779 I assume the person above meant statics in a function, as it was said in the context of Meyers Singleton which is relying on this property to work.
@rinket7779
@rinket7779 3 ай бұрын
@@flyinginthedark6188 ah ok, makes more sense. Curious how initializating a function static could "tank performance" though, sounds very odd.
@rbaron7352
@rbaron7352 Ай бұрын
After working on a code base that was developed for years by the same group of software engineers, I can confidently say that group would definitely just set the global pointer to null and not care if any memory were leaked. One of the persistent bugs that I fixed in that code base was one such issue. Another was one in which a global value which should have been the length an an array was set to an positive error code that was in the same range as would be expected in the array. Both of these bugs were written and tested by profession software engineers with years of experience.
@etherweb6796
@etherweb6796 Ай бұрын
So a lot of these things are addressable using the techniques discussed in "untangling lifetimes" by Ryan Fleury. Resources that are opened by a program are released by the OS after the program ends execution, but additionally this can be handled by simply using arenas to allocate things. (In C++ this means using a custom allocator with either placement new and/or overloads of new and delete for your classes)
@minastaros
@minastaros Ай бұрын
25:00 yes, always _static_ for module-only objects. It's not only a functional thing, but also a _visual_ aid for the reader: ah, this is module-local. Great video, good to hone my skills from time to time!
@Leonhart_93
@Leonhart_93 2 ай бұрын
You tie things together only when they belong together. There are always reasons to have things that are accessible from everywhere for clear reason, that's why the "static" keyword exists.
@steel0tsunami
@steel0tsunami Ай бұрын
I really liked this style of video, I don’t do much OO programming, but one place I like singletons is for the Null objects when using the Null object pattern.
@dimanarinull9122
@dimanarinull9122 Ай бұрын
person: doesn't understand the use cases for a design pattern. person: this is bad because what if you made using design pattern? therefore it's bad and you shouldn't use it. person: hashtag I'm very smart.
@bebe8090
@bebe8090 Ай бұрын
At my work, we follow something similar to the MISRA standard. MISRA says that you shall not allocate dynamic memory in your programs. Ours is a little more lenient, and we can allocate dynamic memory only when the program starts up but not during runtime. So we initialize all of our singletons right away, and there is no risk of one of our classes being deconstructed and remade later. If you're doing something like this, all singletons really do is make it so you don't have to pass a pointer to whatever class needs it, and it can just call on the singleton instead. You don't really have all of the downsides like lazy loading, potentially deconstructing something and worrying if it is available, or whatever else. Really the only negative would be that someone could use it where it shouldn't be used, but I'll accept being a little more vigilant on code reviews watching out for that since it makes things a lot easier. Thank you friend.
@azemu
@azemu 3 ай бұрын
a video on how you do assert would be awesome! Especially the dialogue boxes and such.
@juanmacias5922
@juanmacias5922 3 ай бұрын
Code review series has been solid gold! :D
@paherbst524
@paherbst524 3 ай бұрын
I've been trying to push this w my team. That if you think you need a Singleton, consider just using a namespace.
@jkrigelman
@jkrigelman 3 ай бұрын
We are not software guys but we do a lot of Python for my previous job. I argued with someone on multiple occasions why I removed the Singleton classes and how it was breaking our testing and how it affected real work flows.
@uliwitness
@uliwitness Ай бұрын
Great explanation! IMO the most obvious argument against singletons is that it hurts testability. If you have "the tree", you can create a new instance of the singleton for each test, and destroy it at the end, making sure one test can't screw up the next test. Also, it makes mocking the singleton class much easier if it's just an object you pass in, and you don't have to add a setter for the singleton only intended for use in tests. While I am also generally for predictable construction/destruction, in many applications, you end up doing a surprising amount of work just destructing objects when a process terminates, even though a modern OS will clean up your app's heap anyway. So to improve termination times, you actually might want to only very selectively clean up. (E.g. on macOS, there is an "applicationWillTerminate" notification that you can use to e.g. persist data to disk when the app quits, but e.g. NSApplication's destructor will never be called).
@kanecassidy9126
@kanecassidy9126 3 ай бұрын
yes, please make a video about asserts and error checking/logging
@charliecooper9834
@charliecooper9834 2 ай бұрын
I would love a video on setting up a robust assert system
@d.g.m.04
@d.g.m.04 10 күн бұрын
Hi, there is one thing you forgot to mention that is really bad about the singleton pattern when using the static "stack" initialization inside a function and returning a reference to it. We start from the fact that we want to prevent Static Initialization Order Fiasco, and yeah this code does it - but what it also does is it allows for Static DEInitialization Order Fiasco to happen! This bug is as hideus (if not more) as the SIOF case, and the worst part is that it's usually hard to test for. I struggled for like an hour recently to make a simple test that shows you can crash your program with it, and yes you can! Another thing is that there is a nifty counter approach that I think might be the best of them all but it has some non-trivial costs. Usually you can get away with "leaking" the memory using heap allocation when the destructor call is side-effect free and you don't want to free the thing until the program is done.
@ProGaming-kb9io
@ProGaming-kb9io 3 ай бұрын
Feel better Cherno!
@mr.anderson5077
@mr.anderson5077 3 ай бұрын
Also please create a stand alone series on all kinds of design patterns and their real world use cases
@marcsh_dev
@marcsh_dev 2 ай бұрын
I was ready to come in and argue, but yeah, Id agree with not using a Meyers Singleton for game style resource managers. Resource managers are a nice system to explicitely instantiate early. They often run threads for various processes, look at varoius directories to get a feel for what resources are around, etc, and having that happen at a very well defined time is solid.
@twigsagan3857
@twigsagan3857 13 күн бұрын
Singleton are definitely an antipattern, however. I work in Apex in Salesforce, and the only elegant solution I could come up with to actually test well and mock a database were singletons. For instance. A controller has to be static, so how can you then inject a database mock instead of the real thing. Well I now interchange them if in your test you call getInstance on the subclass mock first, you get the mock, if you call it on the real class you'll get that instead. Works perfectly. But yeah it also means hard coupling a little bit.
@Cruxics
@Cruxics 3 ай бұрын
Another one to throw my interns. Very nice. As far as why you would do something hazardous. It happens. I've had less experienced engineers re-init the entire damn thing because they didn't understand what they were looking at or the naming was just not what they expected which happens in 20+ year old engine code, whether its madden or call of duty as the coding by contract makes a dumb move and decides naming conventions should change. Worse still introducing new language features when moving from a C only engine to a C++ one. Which has happened a few times in my career. Or the stacking of lazily converted code from Action Script, to C#/XNA, to its final form of C++, using a converter instead of rewriting the code properly. So writing defensively and using the tools the language provides to communicate not just intent, but have the compiler guide against unwanted actions with access modifiers and scope limiters can go a long way in preventing someone from squashing your football texture every other frame from a thread not controlled by the rendering thread causing the damn thing to sporadically flicker in game play, because someone decided to re-initialize in the wrong memory pool. Leaving two different jira's for "why is the load time longer" and "why is the ball flickering" at opposite ends of the studio.
@denysmaistruk4679
@denysmaistruk4679 3 ай бұрын
The solution to any problem one is trying to solve ALWAYS depends on CONTEXT. No pattern is universally good for every use case. There are cases where a singleton is not applicable, just as there are cases where a singleton is a good match, perhaps even the best. As a programmer, you should understand the design and requirements of your application and make decisions based on this context. Design patterns are a very controversial topic in general. If someone calls a singleton an antipattern, it doesn’t mean you should never use it. It’s similar to the advice against using the friend keyword or dynamic_cast in your code. It’s unwise to think about such design challenges in a black-and-white manner; there is no silver bullet.
@K3rhos
@K3rhos 3 ай бұрын
Yep, I agree, and also, there is is huge difference when you have the entire program source code and you can actually just follow a clean structure to access things and when you mod the program (game modding for example), you don't know about the whole program but just took some instances you needed here and here, so in game modding it's pretty common to have a LOT of globals variables (and eventually wrap that as a singleton structure) using a custom injected dll. (By getting stuff from here and here in the program without having the entire "application tree", reverse engineering is already pretty hard, so it's common to not know about the entire program structure !)
@pharoah327
@pharoah327 2 ай бұрын
Agree 100%! I hate blanket advice such as "never use this...". In my mind it almost always points to a novice developer who hasn't been in enough situations to realize that everything has its place. Programming is not black and white and design decisions are not universal for all projects.
@MichaelPohoreski
@MichaelPohoreski 2 ай бұрын
Agreed. An expert is one who knows *when* and *why* to break the rules of thumb. EVERY solution is a trade off; being pragmatic means you understand the pros and cons of (most) potential solutions for the problem at hand.
@dbattleaxe
@dbattleaxe Ай бұрын
Agreed. I've used singletons to hold information about the program's layout in memory to find function pointer offsets in order to communicate function pointers across address spaces with ASLR enabled. That's truly a global state. This was to build an RPC system for distributed memory programming. I could have had a global init() call but that's also a global state. Information like the program's layout in memory is an inherently global state so it makes sense. You just tend not to run into inherently global information like that unless you're doing something quite advanced.
@user-wo5dm8ci1g
@user-wo5dm8ci1g 2 ай бұрын
It seems there is an entire dimension related to threading missing here. Lazy loading using that basic singleton pattern without memory barriers can be dangerous. Adding a barrier would make it safe at a fairly high cost to access, but might be worth it if that cost is paid for all the locking needed to synchronize the data structure. Loading early (like in your static example) allows you to do it before threads start accessing it, so its generally way easier to do it that way.
@sacredgeometry
@sacredgeometry 3 ай бұрын
Singletons are fine ... the problem with singletons I have found especially whilst trying to help people in the Unity space is its often a crutch for people who have no idea how to architect their code. Often if you go to unity answers you will see people using it because they have literally no idea how to pass references around or call code on one object from another and their solution is a boat load of singletons. Its painful. There is a definite set of cases where you want to insure there is only ever a single instance of a class at runtime ... half of peoples uses are not it.
@teamdoodz
@teamdoodz 3 ай бұрын
100% agree. singletons are not the devil, theyre just abused
@CodeStructureTalk
@CodeStructureTalk 3 ай бұрын
My guess is that this crutch is still going to be much easier to fix though. If the developers do create this singleton and it keeps all the data, it's much easier to later efficiently map it to GPU buffers to arrive at a very high throughput application. If the developers used shared_ptrs instead to pass small arrays 5 layers down via dependency injection... Well... That's it. The architecture is done, no coming back from that one.
@wb3904
@wb3904 3 ай бұрын
@@sacredgeometry exactly most times people don't know who should own the instance or where it should live, so grabbing it through a singleton looks like a perfectly valid solution. It's an architectural issue for sure. Singletons souls be avoided as they are mostly a sign of architectural problems.
@sacredgeometry
@sacredgeometry 3 ай бұрын
@@CodeStructureTalk Oh absolutely not its a recipe for total spaghetti. Like most "I am doing it this way because I don't know how to do it properly" solutions.
@devluz
@devluz 2 ай бұрын
The average unity app is probably the best example for the downsides of singletons. I work on a very code heavy unity asset which my customers can import into their project and use via a C# API. A lot of my bug reports come from them hacking their own singleton into my C# module and once they update the asset to the latest version it gets overwritten and the house of cards collapses ... Instead of registering a C# side event handler they edit my class that emits the event and forward it directly to their singleton... Absolute madness.
@miikavihersaari3104
@miikavihersaari3104 Ай бұрын
Saying that global variables are bad misses what's relevant a bit. What's relevant is control over time and place of both init and term, and scope of access. Our basic building blocks are types (primitives and structs, i.e. descriptions of data), variables (data stored in memory that is of a type) and functions (a sequence of instructions that operates on data). All of these have a scope, the highest of which is global. Whatever the scope of a single variable is, you need to have explicit control over what happens to it and when.
@TryboBike
@TryboBike 2 ай бұрын
Is 2 million LOC project 'large'? Singletons are my bread and butter, however of a very specific kind and there are a few caveats to their use. First of all - singleton _is a_ global variable with extra steps. Singletons which rely on static storage are asking for trouble, because at some point one singleton will ask for features provided by another and that is going head-first into static initialization fiasco. I could tell you horror stories about this. At one point I refactored the application I work on to have an explicit singleton initializer that is run at the start of the application, which is also explicitly destroyed at the application exit. This works really well. In my experience - singletons make sense for avoiding link-time dependancies and for systems which ought to be accessible throughout the application and in its whole lifecycle. Essentially - a singleton becomes a 'namespace' that can hold some state ( like a cache or somesuch ).
@kiyasuihito
@kiyasuihito Ай бұрын
I found singletons to be a life saver when working with old legacy spaghetti code lol i prefer not to use them either usually, but when the old code your boss hands down to you has messy inlcude dependencies, they can help keep the code you add to the codebase encapsulated in one testable class. Great video. 😂💐
@LuRybz
@LuRybz 23 күн бұрын
I am not from C++ (yet) but in Unity we also have this discussion and Singletons are specially super used. In resume the main point is that Singleton usually breaks any kind of Single Entry Point or Tree Structured architecture and just gives goes for a Global things floating around the code. At first Singleton feels like a clean code thing because your code gets really short and super simple to understand but one or two steps beyond and you will notice one of the problems: having multiple objects with its own decoupled "universe" don´t caring about anything else and just floating in the scene is really problematic if you have to deal with the order of the execution. Usually, you need something ready at some point in the code but it is not in the state that you desired at that point in the code and you have no control over it so you end up creating some kind of event to listen and then you do your logic, and it create eve more global events spread in the code and you and up super decoupled and clean instances but a real spaghetti of events connecting those things. But there are some cases that I am not sure how to not use a Singleton. For Example, an AudioPlayer in the game seems like a good case for a Singleton for me (but I am open to other ideas) and also Login that can be used in different scenes of one App, specially a mobile Game, it feels like a Singleton for the Login could work but I am not sure. Single Entry Point and Dependency Injection is helping me a lot to avoid Singleton and Global things in the code. All that said, I use Singletons with no resctrictions in small projects that I absolutely know it is not going to scale.
@Johan-rm6ec
@Johan-rm6ec 24 күн бұрын
We learn every day🎉
@Mystixor
@Mystixor 3 ай бұрын
Here to let you know I'd love to hear some thoughts on asserts
@istvanszennai5209
@istvanszennai5209 Ай бұрын
of course this would be the desired pattern in a newly developed application. However when you have to work with an already existing 10+ million lines of code mess then singletons bcome pretty handy. To tell an actual example: imagine that you would like to pass data between "far away" modules and it also has to be accessible from the "spine code". One option would be going the long way and modify all those 20+ classes that the data goes through... Or I can just create/use an existing singleton class to bypass all that otherwise unrelated stuff. I chose the latter in a way that it's clear from the code that it is bypassing things (added a BRIDGE_ prefix to these methods).
@berzurkfury
@berzurkfury 3 ай бұрын
The 2 place I use this type of singleton is the application composition root/dependency injection and globalized message resources. Otherwise everything else if needing a singleton behavior, my DI knows which ones will be served as a singleton under an interface
@Omnifarious0
@Omnifarious0 3 ай бұрын
27:28 - It's only sort of thread safe. Only in that if you can make sure you only call Init from one thread, then it's thread safe. And even then, not really. Because getInstance() from any other thread might still return nullptr even if You know for sure that some other thread has called Init and Init has finished running. And that can be the source of some extremely maddening bugs. In order to make it thread safe, the instance pointer as to be atomic or it has to be protected by a mutex. Also, the init/shutdown thing is error prone. You should make RAII do that. I don't like your global variable, and I would do it rather differently. I can understand your concern about lifetime control, and it is an important concern. And so your objection to the Meyers Singleton makes a lot of sense. But I would replace it with something a lot different than what you replaced it with.
@dbattleaxe
@dbattleaxe Ай бұрын
The initialization of static local variables is thread safe as of C++11. The standard states: "If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once). Note: usual implementations of this feature use variants of the double-checked locking pattern, which reduces runtime overhead for already-initialized local statics to a single non-atomic boolean comparison."
@Omnifarious0
@Omnifarious0 Ай бұрын
@@dbattleaxe - Exactly. That's one reason why the Meyer's Singleton is such a good idea so much of the time. He replaces it with something that doesn't have that guarantee.
@vedqiibyol
@vedqiibyol 2 ай бұрын
I usually use singleton as structs, I like to use singleton when I have a rather complex bunch of unique data, like for example when you're making an algorith, your input can be a singleton that you pass around just as a struct, or use singletons as an added namespace, structs make everything public by default, this way it really acts as a namespace
@fejimush
@fejimush Ай бұрын
Another reason not to use Meyer’s double checked locking implementation is that it is not guaranteed to be thread safe prior to C++11. There are plenty of critical applications that depend on older tool chains. Also, (prior to C++11) Arm based cpus that support instruction reordering optimizations at compile, link, and runtime make the Meyer singleton problematic without a memory barrier.
@ilyashcherbakov4115
@ilyashcherbakov4115 3 ай бұрын
DISCLAMER! I am not promoting Singletons. Now, that this is out of the way: It’s fine that you don’t like Singletons, but the alternative you suggested are not very good IMO. You didn’t suggest the tree architecture for the resource manager - you suggested making it global and to provide Init and Destroy functions. So you are providing an API for resource manager that is supposed to be easy to use. Or rather is should be hard to use wrong. But I find that as I client of such API I would start asking questions on how to use it properly. For instance, what happens if I call Init twice? Will it invalidate all the resources that I’ve loaded thus far or do nothing? (I’m not even sure what is the correct thing to do when I call Init twice) Do I need additional API for IsInitialized? Would I need to call IsInitialized every time I need to use the Resources Manager? What happens if I call Destroy twice? I think you see the point. As a “client” of that API I really shouldn’t worry about those things. This adds a lot of manual management to the client, that needs to control when to call Init and Destroy. And since it’s global, technically any one can use this API. P.S. I think the part about lazy loading is irrelevant, since it’s an implementation detail, and can be also done regardless of class lifetime, be it global or singleton. I know you just gave an example of Mayers Singleton being lazy initialized ( hence another name is Lazy Singleton ), but it doesn’t mean that resource need to be lazy loaded as well. Sorry for the long rant. Here’s a potato(. ‘)
@Internetzspacezshipz
@Internetzspacezshipz 3 ай бұрын
I feel like a lot of times in programming we end up with issues like these, where by attempting to avoid a certain “easy but lazy” style of code, we end up with a “complex and confusing” style of code instead. All I’m gonna say is that everything is a lot easier when you have all the source code for everything you’re using, so if you ever have any deep implementation questions like “what if I call Init() twice?”, you can go answer them yourself by looking at the code. Maybe I am a bit biased, since this is how Unreal Engine is set up… but when I go to work inside Unity or anywhere else the source isn’t always immediately available, I find it to be frustrating to try and use an API that inexplicably doesn’t work how you want it to… then you go and google online and find “oh yeah this doesn’t work”, or “oh you need to do it this way” etc etc, when I could have just stepped into the functions I’m calling to figure out the problem. But really that’s just my preference and experience; seeing all the source of the API you’re using makes life way easier, whether the API is complex and confusing or easy but lazy.
@ilyashcherbakov4115
@ilyashcherbakov4115 3 ай бұрын
@@Internetzspacezshipz you would think that having all the source code is good enough, but when you have a 10M loc codebase - you don’t really want to start debugging it. But the main point is that the API should have a well documented and expected behaviour, that is hard to misuse. If you want to tinker with it an debug it - feel free to do so, but it shouldn’t be a requirement. I don’t think that Init() and Destroy() are a very C++ y way of designing an API. It’s more of a C style when you know you need to release the memory you’ve allocated or close a file handle that you’ve opened. We have RAII for this ( speaking of APIs - horrible name ) Ever seen a std::vector call to empty() that ignores the return value because the developer clearly intended to make it empty, instead of calling clear()?
@Internetzspacezshipz
@Internetzspacezshipz 3 ай бұрын
@@ilyashcherbakov4115 Oh yeah, std::vector is definitely bottom tier as far as clear naming of functions goes. I’ve 100% made that exact mistake myself. This is where variable and function naming is critical… IsEmpty(), or how about just not having this function exist? Lol. .size()
@almightysapling
@almightysapling 3 ай бұрын
​@@ilyashcherbakov4115oh God vector.empty()... I try to follow (but often fail) that all "checking" functions take the form of a question, in this case it would be isEmpty(). Not groundbreaking or original, but verbs should verb.
@abcabc-ur3bf
@abcabc-ur3bf 3 ай бұрын
Lean Inversion of Control (IoC) and Dependency Injection. This type of problem has been solved many many years ago...
@blarghblargh
@blarghblargh 2 ай бұрын
It was solved before that, too, by structured programming. Objects and highly flexible composition (especially at runtime) are both massively oversold.
@RedSilencer
@RedSilencer 3 ай бұрын
new phrase unlocked 15:52 more c plus plussy
@nativeme2143
@nativeme2143 Ай бұрын
Hmm, i like to use these in embedded environment ie. where i have "Servo motor" in my device, and i am sure there will be no more other servo motors, usually i am maing key elements like that singleton. Other situation is like mentioned some kind of "Controller", "Manager", "Provider" classes that usually group other entities in system and they are at the top of the hierarchy.
@TOAOGG
@TOAOGG 3 ай бұрын
Singletons are often used instead of dependency inversion. Real pain to test that
@brokensythe
@brokensythe Ай бұрын
The singleton is definitely not an antipattern. An obvious example of when you would want to use this pattern is creating a connection pool. Generally speaking you probably don't want multiple connection pools but also don't want to initialize the pool until you really need it.
@yrucoding
@yrucoding 3 ай бұрын
alot of time when your process is terminating, you might not really want to call destructor on everything…. Consider your process use lots of memory, and your OS has to do some paging to disk. Now when ur process is terminating, do you really want to call every destructor, to just say free up my memory. (at the cost of paging memory back from disk). The OS already does book keeping about the memory used for a process. So you dont have to individually cleanup every object, let the OS do it for you. Unless you have some external resource cleanup that OS processor terminating wont cleanup for you.
@madpuppet666
@madpuppet666 Ай бұрын
I use a module singleton system with each module having a priority and the game engine guarantees all modules init in priority order, and then shutdown in the reverse order. The initlialization order is just too critical, plus a module system gives a formal framework that all singletons will obey. It also means you have a list of all modules that can be accessed and queried which can be useful for various debugging systems. I would never use the meyers pattern for the same reasons discussed in the video. I am a big fan of singletons . There's nothing that complicates your code more than having to pass around accessors to system managers and find at some leaf in your 'tree' you simply can't do some things without refactoring the entire tree to give you access to a module. You never have to 'shutdown' a singleton. Thats the whole point of them. You never have to shutdown your inputmanager. Its viable for large programs. Unreal Engine is built on a bunch of singletons. Not saying its perfectly designed, but saying singletons only work on small programs is absurd. You still need to design your singleton acess intelligently.
@SownJevan
@SownJevan 3 ай бұрын
The lighting on this video is very good.
@WoodySTP
@WoodySTP 3 ай бұрын
One really big reason why i hate singletons from a non game dev is that it's a pain to test. You hold state that is hard to manipulate once you created an instance.
@oliver_twistor
@oliver_twistor 2 ай бұрын
Good discussion! As an unwashed Java developer, I can't really comment on the C++ specific stuff, but I agree that singletons should generally be avoided (or at least used with caution) regardless of language used.
@kamilkopryk8572
@kamilkopryk8572 3 ай бұрын
Another problem with Scott Meyers singletons can happen when one singleton is being destroyed and tries to use another singleton that was created in a different translation unit. If that other singleton has already been destroyed (the order of destruction for static objects across different translation units is undefined), it can cause the application to crash
@codemetas1284
@codemetas1284 3 ай бұрын
That is true for static variables in some namespace. It is *not* true for function scope statics(as used in Meyers singletons), where the order is well defined. Those are destroyed in reverse order of their construction. See basic.start.term in the current c++ standard: "If the completion of the constructor or dynamic initialization of an object with static storage duration strongly happens before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first." (I seem to recall that is true since C++11, but am slightly unsure about that) Even with a defined order so, I agree that it might still cause problems and it is - in most cases - likely best to avoid such slightly hidden dependencies.
@tolkienfan1972
@tolkienfan1972 3 ай бұрын
​@@codemetas1284op is correct. There is no guarantee an object that refers to the singleton was constructed before or after it, or even during. You have to ensure the constirction order explicitly.
@nicholaskomsa1777
@nicholaskomsa1777 7 күн бұрын
The first time you call that singleton, it is constructed right there. Therefore, to use them with a specific setup order, you should simply call the singleton collection in your setup routine. I make use of singletons though not that kind which I also do not like a lot. If you have to use singletons, the use-case is almost always with a collection of interdependent stuff, so you would always need a specific static setup routine.
@Adam_Lyskawa
@Adam_Lyskawa 3 ай бұрын
I like how you explained the "costs" and down sides of singletons. I don't dislike them, I'm neutral towards them ;) Maybe because I work alone and I don't have to deal with someone else's messy code too often. In some contexts a singleton is just a tool like everything else. I use them mostly in one case: when I want zero or one instance, and I want to be initialized on first use, if it is used at all. So - obviously if an external factor (like a user input) decides whether my object would be used at all. So, a ResourceManager class doesn't fit that scenario, it's something that is a dependency for basically everything else. Of course the singleton can be replaced with a global variable, but when we sorted out our initialization / cleanup issues - the difference is purely cosmetic so it's a matter of what is more pleasing to your eyes ;)
@etherweb6796
@etherweb6796 Ай бұрын
I dunno - pretty sure that no matter what "mixins", and multiple inheritance are anti patterns - the downsides outweigh any benefit
@Hersatz
@Hersatz 2 ай бұрын
Like for any type of global-ish access pattern, singleton is good to use in specific contexts. Same applies for Services and Signals and Dependency injection, and whatever else architectures. The way I see it, the issue is that they look like an easy solution to a lot of complex problems. Hence why, I suppose, we see so many "anti-pattern" usage stemming from it.
@Omnifarious0
@Omnifarious0 3 ай бұрын
13:55 - There is another reason Singleton is bad. Singleton is, effectively, a global variable. And the problem with a global variable is that you have no idea who touches it or when. This can make the behavior of your program much harder to reason about because it depends on non-local effects. It also makes testing really hard. If you want to mock out part of your system so you can test other parts with a 'fake' system, a Singleton makes that nearly impossible.
@mike200017
@mike200017 3 ай бұрын
I'm definitely on the side of avoiding global mutable state at all costs. One crucial aspect that was not mentioned in the "tree" explanation is testability and scalability. This may be less common in computer games, but in general, if any of your components depend on singletons (or any other form of global mutable state) it becomes really frustrating and hard to write good test suites for the components (because you have to always setup and correctly reset the required global state) and it becomes hard to scale it in the sense that you can't instantiate multiple standalone "trees". I don't like this init/shutdown solution. In my experience, if you have some quasi-global state that you need to create and destroy at specific points, you should not make it a singleton, just pass it down to whatever objects need it and properly tie together the life-times. And in the rare cases where it might be appropriate to have a singleton, it's very important that all uses of that singleton are idempotent, that is, it is irrelevant (as far as what matters to you), at the point of use, what the state of the singleton is. In the case of resource loading, if it matters whether or not a resource has already been loaded (because of dropping frames, as explained), then a "lazy" singleton is not idempotent. The only kind of singleton that still allows for testable and scalable code is an idempotent singleton. I have generally reached the conclusion, after decades of experience, that the design space that exists between the idempotent/lazy singleton and the "fly-weight" object (i.e., the quasi-global object passed down to the rest of the "tree") is vanishingly small.
@FJhunman
@FJhunman 3 ай бұрын
I am not particularly a fan of singletons, but they do have their advantages. Sadly some of my colleagues (who mostly work with Unity and C#) seem VERY fond of them and use them everywhere they can, making testing somewhat of a nightmare in our project in the past. In your (final) example I don't like how you have to explicitly call a shutdown function, I would probably write a RAII-style initter and deinitter (with [nodiscard], since I am elevating that warning into an error with compiler flags), but that's just me.
@DLCSpider
@DLCSpider Ай бұрын
You can tell him that Eric Lippert, a former member of the C# commitee, once said that singletons are only okay when their removal doesn't change the output of your program (e.g. loggers, grabage collectors).
@eduameli
@eduameli 3 ай бұрын
do a video about the asserts pls
@Southpaw17
@Southpaw17 3 ай бұрын
Interesting. My company is currently in the process of completely moving _away_ from the "Two-Phase Init" paradigm that you're advocating for here. While my takeaway from this video is still "Avoid Singletons as a rule, but if you need to use one, here is the least bad way," I would have liked to see you go into more detail about the issues that arise from adopting this pattern. Specifically, the initialization hell that occurs from manually init''ing several Singletons at the top of your application stack or what happens when one Singleton depends upon another (and thus imposes an invisible ordering on the series of Init calls)
@almightysapling
@almightysapling 3 ай бұрын
I don't think "avoided" is the right word, I just think the use case for them is extremely niche and the typical things people think would make a good reason to have a Singleton are the wrong reasons. I mean, "My program should only have one of these" seems like a damn good reason, but it almost always isn't.
@Southpaw17
@Southpaw17 3 ай бұрын
@@almightysapling "Prefer" and "Avoid" are pretty common terms in coding standards docs, which is why I chose that particular word here. Typically "Avoid" doesn't mean something is banned, just discouraged, and its use may require additional review or approval by your leads
@daydreamer0606
@daydreamer0606 3 ай бұрын
Thanks
@mr.anderson5077
@mr.anderson5077 3 ай бұрын
Get well soon Sensei
@Navhkrin
@Navhkrin 3 ай бұрын
I very much like Unreal's subsystem system for this; It works very much like singleton but its lifetime is well maintained based on what kind of subsystem it is. For example, a world subsystem will be created and will die with the world. And you can access this subsystem from a pointer to the world. This pattern isn't too difficult to implement on a custom engine either, a little bit more work than singletons but you get benefits of well maintained lifetimes. My only caveat with it is that we can't do replication from these, because replication is strongly tied to actors in Unreal which imo is not the best idea.
@kidmosey
@kidmosey 3 ай бұрын
Nearly every singleton I've designed eventually needed multiple instances.
@pharoah327
@pharoah327 2 ай бұрын
Can't say I agree. I've never had this issue and I've worked on several projects that took years to create (with changing requirements). But to each his own.
@kidmosey
@kidmosey 2 ай бұрын
@@pharoah327 For starters, it forces you to do your unit tests synchronously.
@pharoah327
@pharoah327 2 ай бұрын
@@kidmosey every framework I've used for unit testing has been synchronous by default. So I don't have experience with that but that is interesting. I'll have to look into how to do unit tests in an async manner. Even still, I imagine not all unit tests will need to touch the Singleton, therefore many can still be done in parallel. So that alone definitely doesn't make singletons bad. Every design decision will have pros and cons. Every single one. So just mentioning a con (or even several cons) doesn't immediately invalidate a technique for all purposes.
@megadodd
@megadodd 3 ай бұрын
One practical reason to avoid singletons, especially in more complex systems, is related to how static memory is handled in real-world applications. When different executables or DLLs use the same static library, each creates its own copy of the static data. This means that even though they use the same library, they do not share the same memory instances for global or static variables. As a result, if an executable loads a DLL, any singletons defined in the static library will be unique to each module. This can lead to situations where the executable and the DLL, which are supposed to work on shared data, are actually working with separate copies, causing inconsistencies and unexpected behavior.
@death-sign
@death-sign 3 ай бұрын
Correct me if I'm wrong. But I believe using an anonymous namespace or "unnamed" namespace would provide the same functionality but keeping an instance shared.
@alphenex8974
@alphenex8974 3 ай бұрын
I am pretty sure you can easily prevent it using specific keywords so it should not be a problem
@piootrk
@piootrk 3 ай бұрын
This was my first thought when I saw 'static' version of singleton code. It is a nightmare in Windows program.
@anon_y_mousse
@anon_y_mousse 2 ай бұрын
One minor problem with your weapon example is that the game should load it when it loads the map. Every game I've seen does it like this and everything contained in a given map is listed in the map file. Animations might be different, depending on the game, but ideally, they should all be preloaded too. This isn't intended as a criticism, because I agree with you regarding singletons, but the point could use some strengthening there with a better example. Not that you or anyone else will see this.
@matthewread9001
@matthewread9001 2 ай бұрын
I used singleton for my Random class. That’s it.
@Wo1fie
@Wo1fie Ай бұрын
I'm mostly self taught with C and C++ but why would we not call Init during constructor and Shutdown during destructor, or even just place the code within those methods inside of the constructor and destructors?
@lambdacalculus3385
@lambdacalculus3385 3 ай бұрын
yeah, global instances are sometimes useful but it's not thread safe; i'd just stick singleton pattern actually with using smart pointers; it guarantees thread safety*. * seems access to resource is not thread safe but accessing to object is safe. solution is either using `boost::atomic_shared_ptr` or new template for `std::atomic`, or experimental `std::atomic_shared_ptr`, same thing. if every operation done using `std::atomic`, then there is no problem. comes with c++20! thanks for correction by the way.
@zanagi
@zanagi 3 ай бұрын
So this is just thread specific issue? Any way to learn more about this?
@Firestar-rm8df
@Firestar-rm8df 3 ай бұрын
It's important to note that only the control block is thread safe due to the atomic ref count. The actual access to the managed object is in no way thread safe by default, so make sure you still account for that in your thread safety design. This is a common mistake I still see made by professionals on the daily.
@_lod
@_lod 3 ай бұрын
global smart pointer 🔥🔥🔥
@AlexandreA-w5c
@AlexandreA-w5c 3 ай бұрын
Construction of the singleton is thread safe, not the usage though!
@lambdacalculus3385
@lambdacalculus3385 3 ай бұрын
​@@calvinsomething5348it mostly about TheCherno's implementation is directly creating a static instance. with use of a function, at least you can check for any manual deallocation.
@porky1118
@porky1118 2 ай бұрын
1:00 I already agree. Either just pass things around or use a global variable.
@skale7738
@skale7738 3 ай бұрын
you are 100% right
@mrpocock
@mrpocock 16 күн бұрын
Singletons tend to be a code workaround for not having resource and configuration baked in.
@fredhair
@fredhair 3 ай бұрын
I'm just imagining this on the fly and haven't worked with C++ really for years now but perhaps you could have a Meyer's singleton that holds some weak pointers (e.g. to registered services with service locator pattern); the static destructor checks that services have shutdown correctly i.e. pointers == nullptr? In theory since the static memory is destructed last could it be useful for checking that other systems are shutting down and freeing memory correctly? Could you use it in some sort of new / delete overload to do basic logging where something has failed to free up? Fairly sure I've seen the Meyer's singleton used to create a runtime resolver for DI in C++. Regarding DI; dependency injection can't always just be used to replace singletons, occasionally you may not be in charge of specifying object dependencies / constructors, perhaps call backs or situations where there is requirement that the client implements a certain forward declared function - you have to work with limited scope of dependencies. Sometimes you may want a lazy loaded Meyer's singleton for this situation? Probably pretty niche uses but always try use the right tool for the job; which at times will be Meyer's singleton.
@electricspeedkiller8950
@electricspeedkiller8950 3 ай бұрын
I use them often for a class that holds data for resources and such.
@thebatchicle3429
@thebatchicle3429 3 ай бұрын
Just use a global struct
@wb3904
@wb3904 3 ай бұрын
You've been indoctrinated to think that everything just be a class. A singleton is basically a single global structure.
@PopescuAlexandruCristian
@PopescuAlexandruCristian 3 ай бұрын
I am 100% with you on this, but if you think like this why do you use shared pointers, how can you accept does
@TruthAndLoyalty
@TruthAndLoyalty Ай бұрын
Just here to say, there is no such thing as an antipattern. All named patterns have pros and cons and are more appropriate in different scenarios. It's pretty rare for a pattern to be universally bad or always reign supreme in every scenario to an extent that alternatives should be called antipatterns. If anything, we should treat them like code smells. As in "this pattern often has problematic usage, there may be a better way to accomplish this" and investigate if that's the case.
@Beatsbasteln
@Beatsbasteln 3 ай бұрын
I'm writing VST plugins in C++ and this whole talk about singletons makes me think of the Utils class in my code base. It is created in the top most component of the graphical interface and then passed on to all child components as a reference. that's like having a pseudo singleton. i couldn't just make it static because then different instances of the same plugin would share the state of its values and often that is not a very good idea. I'm kinda annoyed by the references tbh, because you keep having to repeat yourself in your code about sharing the Utils reference with another class and another class and another class, you know? i wish there was like a soft singleton that I can use in such cases. the initialisation problem might be a little annoying, but ultimatively it would speed up the workflow of adding new code a lot I think. But maybe I'm wrong and I should be happy that I never had to deal with such initialisation bugs yet. maybe they are even worse than having to write the same stuff over and over again.
@godotc
@godotc 3 ай бұрын
The second mthod could not works in a dynamical shared library.
I Rewrote This Entire Main File // Code Review
16:08
The Cherno
Рет қаралды 177 М.
Is C BETTER than C++ for beginners? // Code Review
31:16
The Cherno
Рет қаралды 50 М.
Noodles Eating Challenge, So Magical! So Much Fun#Funnyfamily #Partygames #Funny
00:33
ТЮРЕМЩИК В БОКСЕ! #shorts
00:58
HARD_MMA
Рет қаралды 2,6 МЛН
СКОЛЬКО ПАЛЬЦЕВ ТУТ?
00:16
Masomka
Рет қаралды 3,5 МЛН
Players push long pins through a cardboard box attempting to pop the balloon!
00:31
Why I Use C | Prime Reacts
13:00
ThePrimeTime
Рет қаралды 178 М.
Microservices are Technical Debt
31:59
NeetCodeIO
Рет қаралды 647 М.
Stop using std::vector wrong
23:14
The Cherno
Рет қаралды 150 М.
Qwen Just Casually Started the Local AI Revolution
16:05
Cole Medin
Рет қаралды 89 М.
SOME UNIQUE C++ CODE! // Pacman Clone Code Review
26:42
The Cherno
Рет қаралды 287 М.
WHY did this C++ code FAIL?
38:10
The Cherno
Рет қаралды 294 М.
I Solved 100 LeetCode Problems
13:11
Green Code
Рет қаралды 251 М.
TETRIS CLONE! // Code Review
36:55
The Cherno
Рет қаралды 58 М.
Dependency Injection, The Best Pattern
13:16
CodeAesthetic
Рет қаралды 884 М.
Noodles Eating Challenge, So Magical! So Much Fun#Funnyfamily #Partygames #Funny
00:33