Why I Don't Like Singletons

Поделиться
HTML-код
  • Опубликовано: 16 сен 2024

Комментарии • 352

  • @TheCherno
    @TheCherno  24 дня назад +25

    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 23 дня назад +1

      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 22 дня назад

      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 22 дня назад

      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 22 дня назад +2

      There is an advantage to the static inside the method, it's guaranteed to be thread safe when it's initialized.

    • @guywithknife
      @guywithknife 18 дней назад

      @@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 24 дня назад +430

    Get married and become a Coupleton. Even better, a Factory.

    • @Kokurorokuko
      @Kokurorokuko 24 дня назад +125

      My girlfriend is abstract

    • @felipeliradev
      @felipeliradev 23 дня назад +20

      @@Kokurorokuko💀

    • @soniablanche5672
      @soniablanche5672 23 дня назад +16

      wife becomes a BabyFactory

    • @spiderpig20
      @spiderpig20 23 дня назад +24

      My girlfriend is virtual

    • @JM-is1vf
      @JM-is1vf 23 дня назад +13

      Delete the copy constructor of the Wife class to avoid UB.

  • @GuilhermeTeres
    @GuilhermeTeres 23 дня назад +56

    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 23 дня назад +8

      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 22 дня назад +2

      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 21 день назад

      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 14 дней назад +3

      *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 14 дней назад

      @@ferinzz I fully agree!

  • @patrickgono6043
    @patrickgono6043 21 день назад +7

    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.

  • @JaceMorley
    @JaceMorley 24 дня назад +148

    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 24 дня назад

      ++

    • @Kokurorokuko
      @Kokurorokuko 24 дня назад +5

      ​@@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 23 дня назад +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 23 дня назад +4

      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 23 дня назад +9

      @@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.

  • @zombi1034
    @zombi1034 24 дня назад +28

    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 24 дня назад +4

      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 24 дня назад +1

      But yeah, singletons though enums are cool

    • @emilyy-dev
      @emilyy-dev 24 дня назад +7

      ​@@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 24 дня назад +5

      @@emilyy-dev you are right actually

    • @mr.m8539
      @mr.m8539 21 день назад +6

      ​@@kostiaperegudaJust wanted to you for being adult enough to admit being wrong. The world needs more people like you.

  • @codemetas1284
    @codemetas1284 23 дня назад +67

    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 23 дня назад +2

      Congrats on being featured lol

    • @aloluk
      @aloluk 23 дня назад +1

      Yeah, i'm waiting for any mention of thread safety, part way through video so far.

  • @theo-dr2dz
    @theo-dr2dz 23 дня назад +8

    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.

  • @alexkhazov7264
    @alexkhazov7264 24 дня назад +30

    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 23 дня назад +12

      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 21 день назад +2

      @@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.

  • @jkrigelman
    @jkrigelman 21 день назад +4

    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.

  • @dhickey5919
    @dhickey5919 11 дней назад

    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.

  • @ashleigh.
    @ashleigh. 20 дней назад +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

  • @suryanshusharma3227
    @suryanshusharma3227 24 дня назад +4

    Good take. Need more videos talking about design decisions.

  • @Chukozy
    @Chukozy 23 дня назад +2

    While I can't speak for everyone, we need more of these videos sharing your insights and expertise!

  • @jlr3739
    @jlr3739 24 дня назад +23

    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 22 дня назад +1

      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 16 дней назад

      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.

  • @retakenroots
    @retakenroots 23 дня назад +17

    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 23 дня назад +2

      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 22 дня назад +1

      In my opinion Singletons are just *poorly* wrapped globals. Extra OOP fluff for no gain in safety/utility/readability.

    • @dagoberttrump9290
      @dagoberttrump9290 19 дней назад +1

      same. and DI ftw!

  • @Strazz801
    @Strazz801 22 дня назад

    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.

  • @dct4890
    @dct4890 23 дня назад

    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.

  • @flyinginthedark6188
    @flyinginthedark6188 24 дня назад +12

    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 23 дня назад

      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 22 дня назад +1

      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 22 дня назад

      @@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 21 день назад +1

      @@flyinginthedark6188 ah ok, makes more sense. Curious how initializating a function static could "tank performance" though, sounds very odd.

  • @funkdiscgolf
    @funkdiscgolf 23 дня назад +1

    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!

  • @Spongman
    @Spongman 21 день назад +5

    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.

  • @paherbst524
    @paherbst524 24 дня назад +5

    I've been trying to push this w my team. That if you think you need a Singleton, consider just using a namespace.

  • @doug9000
    @doug9000 4 дня назад +1

    Its amazing how some programmers invent a new world for a thing (global variable) and pretend is a new technique given by the gods.

  • @Smartskaft2
    @Smartskaft2 21 день назад +2

    No mentioning of it instantly bulldozing the testability of your system? 😢

  • @duncangibson6277
    @duncangibson6277 24 дня назад

    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 👍

  • @marcsh_dev
    @marcsh_dev День назад

    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.

  • @kolosso305
    @kolosso305 24 дня назад

    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!

  • @Splntxx
    @Splntxx 24 дня назад +3

    Really nice video quality - lighting and angle is perfect!

  • @RedSilencer
    @RedSilencer 22 дня назад +1

    new phrase unlocked 15:52 more c plus plussy

  • @denysmaistruk4679
    @denysmaistruk4679 24 дня назад +4

    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 20 дней назад +4

      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 16 дней назад +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 дня назад +1

      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.

  • @user-wo5dm8ci1g
    @user-wo5dm8ci1g День назад

    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.

  • @Omnifarious0
    @Omnifarious0 23 дня назад +1

    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.

  • @juanmacias5922
    @juanmacias5922 23 дня назад

    Code review series has been solid gold! :D

  • @WoodySTP
    @WoodySTP 23 дня назад +1

    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.

  • @vedqiibyol
    @vedqiibyol 16 дней назад

    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

  • @abcabc-ur3bf
    @abcabc-ur3bf 21 день назад +1

    Lean Inversion of Control (IoC) and Dependency Injection. This type of problem has been solved many many years ago...

    • @blarghblargh
      @blarghblargh 5 дней назад

      It was solved before that, too, by structured programming. Objects and highly flexible composition (especially at runtime) are both massively oversold.

  • @TOAOGG
    @TOAOGG 23 дня назад +1

    Singletons are often used instead of dependency inversion. Real pain to test that

  • @Omnifarious0
    @Omnifarious0 23 дня назад +1

    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.

  • @azemu
    @azemu 22 дня назад

    a video on how you do assert would be awesome! Especially the dialogue boxes and such.

  • @TryboBike
    @TryboBike 8 дней назад

    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 ).

  • @kamilkopryk8572
    @kamilkopryk8572 24 дня назад +9

    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 23 дня назад +2

      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 23 дня назад +2

      ​@@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.

  • @sebbbi2
    @sebbbi2 17 дней назад

    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 17 дней назад

      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.

  • @kidmosey
    @kidmosey 23 дня назад +11

    Nearly every singleton I've designed eventually needed multiple instances.

    • @pharoah327
      @pharoah327 16 дней назад +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 16 дней назад

      @@pharoah327 For starters, it forces you to do your unit tests synchronously.

    • @pharoah327
      @pharoah327 16 дней назад

      @@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.

  • @FJhunman
    @FJhunman 24 дня назад +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.

  • @oracleoftroy
    @oracleoftroy 17 дней назад

    Singletons are pretty much always a bad idea. They really only make sense for when you have an instance of something where you really only want one and it is a truly universally needed component. The only thing that comes to mind that fits the bill somewhat consistantly is logging, and even logging doesn't need to be a singleton.
    I don't think even an application class should be a singleton. If a subcomponent needs a back pointer to the app, just pass in a back pointer to the app.
    Too many people confuse "I only need one instance" with "I need a singleton." No, you just need to create one. You almost never need to force your program to disallow multiple instances.
    Moreover, it makes it a lot clearer when you explicitly pass around dependencies rsther than just reach into global state for them. Sometimes people point out that this can get unwieldy, but i look at that as revealing that you have made spegetti of your state, and you need to simplify. Making it global complicates it and hides the complication under a rug, usually making code that is harder to test, to reason about, to use in a parallel context, and often has tricky life time issues that just arent obvious.

  • @anon_y_mousse
    @anon_y_mousse 5 дней назад

    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.

  • @ilyashcherbakov4115
    @ilyashcherbakov4115 23 дня назад +16

    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 23 дня назад +2

      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 23 дня назад +1

      @@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 23 дня назад +1

      @@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 22 дня назад

      ​@@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.

  • @Mad3011
    @Mad3011 20 дней назад +1

    Singletons are just globals in disguise, fight me. At least for the latter the consensus is that they are better avoided. There are situations where it makes sense to have a global variable but these are very very rare. When I see Singletons used, they tend to infect the whole codebase, spread like a plague, couple everything together and make testing and code reuse virtually impossible. They seem to be used in place of an actual thought out architecture. So, IMHO, if you feel the need to use a singleton, just use a global with explicit initialization/shutdown, or better yet think hard about whether you actually need global singular state.

  • @ProGaming-kb9io
    @ProGaming-kb9io 23 дня назад

    Feel better Cherno!

  • @Adam_Lyskawa
    @Adam_Lyskawa 24 дня назад

    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 ;)

  • @Hersatz
    @Hersatz 17 дней назад

    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.

  • @AgentM124
    @AgentM124 23 дня назад +1

    I once had to unit test something.
    Couldn't mock it.
    Couldn't fake it.
    Couldn't do any injection or reflection.
    Complete piece of garbage.
    Hidden caches with static lifetime, which means one unit test can modify another unit test's output.
    :)

  • @sacredgeometry
    @sacredgeometry 24 дня назад +39

    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 24 дня назад +7

      100% agree. singletons are not the devil, theyre just abused

    • @CodeStructureTalk
      @CodeStructureTalk 23 дня назад

      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 21 день назад +1

      @@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 21 день назад

      @@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 18 дней назад +1

      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.

  • @mike200017
    @mike200017 24 дня назад

    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.

  • @Southpaw17
    @Southpaw17 22 дня назад +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 22 дня назад +2

      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 22 дня назад

      @@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

  • @Cruxics
    @Cruxics 24 дня назад

    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.

  • @kanecassidy9126
    @kanecassidy9126 22 дня назад

    yes, please make a video about asserts and error checking/logging

  • @porky1118
    @porky1118 3 дня назад

    I don't even think, it's a pattern used by anyone in Rust.
    I guess the once cell is technically a singleton.

  • @Beatsbasteln
    @Beatsbasteln 24 дня назад +1

    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.

  • @SilverSuperGamer
    @SilverSuperGamer 21 день назад

    Cherno: uses singletons
    Also Cherno: doesn't like singletons

  • @berzurkfury
    @berzurkfury 23 дня назад

    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

  • @creo_one
    @creo_one 23 дня назад

    I've never seen any usage of Singleton that couldn't be replaced by factory method or static factory, latter ones are easier to test, replace and reuse.

  • @Dagrond
    @Dagrond 23 дня назад

    You just explained why 9 out of 10 of the games I play now suck when you change context. LOL. Architecture is such a simple fix ;)

  • @yrucoding
    @yrucoding 23 дня назад

    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.

  • @mr.anderson5077
    @mr.anderson5077 24 дня назад +3

    Also please create a stand alone series on all kinds of design patterns and their real world use cases

  • @megadodd
    @megadodd 24 дня назад +8

    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 24 дня назад +1

      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 24 дня назад

      I am pretty sure you can easily prevent it using specific keywords so it should not be a problem

    • @piootrk
      @piootrk 23 дня назад

      This was my first thought when I saw 'static' version of singleton code. It is a nightmare in Windows program.

  • @SownJevan
    @SownJevan 23 дня назад

    The lighting on this video is very good.

  • @SigmaOfMyParts
    @SigmaOfMyParts 19 дней назад

    hope you are fine. you looked well more healthy some years back

  • @mr.anderson5077
    @mr.anderson5077 24 дня назад

    Get well soon Sensei

  • @dougpark1025
    @dougpark1025 23 дня назад +1

    The init/shutdown pattern is RAII. Your tree like structure for construction and tear down is RAII.

  • @Mystixor
    @Mystixor 23 дня назад

    Here to let you know I'd love to hear some thoughts on asserts

  • @Navhkrin
    @Navhkrin 23 дня назад

    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.

  • @PopescuAlexandruCristian
    @PopescuAlexandruCristian 24 дня назад

    I am 100% with you on this, but if you think like this why do you use shared pointers, how can you accept does

  • @OneAndOnlyMe
    @OneAndOnlyMe 4 дня назад

    You can be a excellent programmer and not be good at math. I know because I am in that position :) I can always look up any math I may need to apply.

  • @blackstarmaster
    @blackstarmaster 24 дня назад +1

    I used a meyer singleton to implement the programs configuration. It basically manages a config file (read/write/access information)
    what would be the clever alternative there? At the moment i use the singleton at various i itializerts and sporadiacally acess it from commandhandler to modify or access config data

  • @daydreamer0606
    @daydreamer0606 23 дня назад

    Thanks

  • @porky1118
    @porky1118 3 дня назад

    1:00 I already agree. Either just pass things around or use a global variable.

  • @fredhair
    @fredhair 23 дня назад

    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.

  • @TheEVEInspiration
    @TheEVEInspiration 14 дней назад

    *Complaining about singletons is misreading the actual problem!*
    Most run-time environments are missing a predictable / "always there" foundation with relevant code and configuration(-pattern).
    This is what singletons use is trying to make up for.
    It's not that singletons are bad, but its that most run-time environments are bad!
    They are typically too low-level for the common usecase!
    A language like Java, C, C# or C++ does not make many assumptions as to what it's going to be used for.
    It could be a console program, OS, a service or a web-api style of project.
    As such they lack a good foundation for all of them and are terrible languages for most of these uses.
    Lacking an appropriate common foundation, which is different from just having a standard function library, every company will have to make its own hacks to simply the work!

  • @ciCCapROSTi
    @ciCCapROSTi 24 дня назад

    There is very rarely a case that dependency injection can't solve and a singleton is a better solution. You don't need a global OR a singleton, just allocate it in the main (or second main that is called from the main, or the Application class, doesn't matter), and pass it.

    • @merseyviking
      @merseyviking 24 дня назад

      Playing devil's advocate here, but a problem with dependency injection like that is if a child class needs a dependency, you have to pass it down through the hierarchy. So the higher level classes look like they need the dependency, but really they don't. Which can make deciphering code a pain when you're not familiar with it.
      I'm not saying singletons are the answer, but the problem can be nuanced.

    • @ciCCapROSTi
      @ciCCapROSTi 23 дня назад +2

      @@merseyviking the solution is that either you have a class that is tightly coupled with the owner class, therefore the owner DOES indeed depend on the dependency, or it's not tightly coupled, in which case itself should be a dependency, not a member.
      Of course this is not clear in every case, but also not THAT difficult.

  • @eduameli
    @eduameli 24 дня назад +7

    do a video about the asserts pls

  • @skale7738
    @skale7738 23 дня назад

    you are 100% right

  • @godotc
    @godotc 21 день назад

    The second mthod could not works in a dynamical shared library.

  • @matthewread9001
    @matthewread9001 9 дней назад

    I used singleton for my Random class. That’s it.

  • @prostmahlzeit
    @prostmahlzeit 23 дня назад

    Do a video about dependency injection in c++ please

  • @lambdacalculus3385
    @lambdacalculus3385 24 дня назад +14

    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 24 дня назад

      So this is just thread specific issue? Any way to learn more about this?

    • @Firestar-rm8df
      @Firestar-rm8df 24 дня назад +4

      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 24 дня назад

      global smart pointer 🔥🔥🔥

    • @AlexandreA-w5c
      @AlexandreA-w5c 24 дня назад +1

      Construction of the singleton is thread safe, not the usage though!

    • @lambdacalculus3385
      @lambdacalculus3385 24 дня назад

      ​@@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.

  • @mr.anderson5077
    @mr.anderson5077 24 дня назад

    Hey Cherno, can you please create a tutorial to write a Arena Allocator from scratch, additionally also describe upon Entity collection system from scratch with the same. Just like you did development in Hazel

  • @danielnavarrete8571
    @danielnavarrete8571 20 дней назад

    Give us that asserts video please!

  • @paulocezar7395
    @paulocezar7395 24 дня назад +2

    What about Service Locator? Seems more appropriate. Specially in C++, due to the lack of Dependency Injection frameworks and Reflection

  • @utilyre
    @utilyre 23 дня назад

    bro cooked this time

  • @Klaus-jr7yr
    @Klaus-jr7yr 23 дня назад

    The one thing that triggers me much more is that you do not use a unique_ptr instead of a raw pointer to store your singelton on the heap. Is there a reason for that?

  • @AliceTurner-q3f
    @AliceTurner-q3f 24 дня назад

    Hey I've changed account and I don't think I've ever actually commented before but I do watch video's. I also don't use C++ but I did a long time ago before other systems languages became available.
    I have to admit a lot of people do dislike Singletons and using statics. I do realise from watching the video that threading and where the static is placed make a difference to the static variable types but, this is why I moved over to Rust.
    With Rust initialisation is done differently and if I may say better. You can create statics and suffer similar problems as with C but the standard library now offers both a Once lock which will create the static by which thread gets to it first and all other threads will wait for it. There is also LazyLock which does what the OnceLock does hit lazily.
    Rust usually has everything one needs but it's as old as C. I probably sound like the meme t this point "just rewrite it in Rust" 😅 but it has it's modern concepts.
    Currently I'm rewriting the global allocator so I can use a static structure to monitor memory usage, while in debug in my ide. It stills allows for the low level control, might be worth while checking out if you get time

  • @brucecrusty
    @brucecrusty 14 дней назад

    If you need to use a singleton, you'll know why. Doing it because you don't know better or because you just learned about it isn't great.

  • @ZapOKill
    @ZapOKill 23 дня назад +2

    Singletons are the worst when it comes to testing.

    • @weaktusk
      @weaktusk 12 дней назад +2

      Since when do game devs write tests?

    • @paulblart7378
      @paulblart7378 День назад +1

      Game devs don’t write tests. They play test.

  • @_CJ_
    @_CJ_ 23 дня назад

    So the main point is about managing resources correctly in correct scope. Nothing to do with singleton. Yea I would not care at all in small single-use project, I would care a lot when doing engine :D I would also let operating system handle shutdown after I saved all I need. I hate when something hangs when closing it for no good reason...

  • @KennyTutorials
    @KennyTutorials 24 дня назад

    Say it short like that guy from the meme "sometimes good, sometimes bad".
    It all depends on use case.

  • @okira_e
    @okira_e 24 дня назад

    Amazing content as usual! I for one would love to see how you handle logging in side the Hazel engine.

  • @mardagit
    @mardagit 23 дня назад +2

    Some people may think that Init + Shutdown may crash their program and that's bad - imagine my case where 5 separate singletons blocked the program from shutting down because they wait for other singletons that relied on multithreaded tasks that couldn't finish because other singletons that managed these tasks were already finished without notifying about the cancellation - and reproduction rate is 1/50 because it was related to auto-generated order of destruction "after main()" so each build could contain a bad order.
    Trust me - you prefer predictable code that you can assert/check and then fix rather than "It used to work, what happened?" code.

    • @user-sl6gn1ss8p
      @user-sl6gn1ss8p 23 дня назад

      Also, this was discussed in the context of a "main system"

  • @matthiskalble3621
    @matthiskalble3621 14 дней назад

    Singletons are just fancy globals

  • @jacko314
    @jacko314 24 дня назад

    I agree. This programming paradigm seems very rigid to me and doesn't lend itself for code reuse/growth.