CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 10 of 10
  1. #1
    Join Date
    Jul 2010
    Posts
    6

    Question Singleton vs Globals vs Alternatives (Best Practice, Design)

    I'm trying to determine the best way of implementing a few things, but the key one is an 'application quitting' method. While this applies to many different projects in different ways, let me explain a good, real-world example:

    A thread is spawned that is tasked with processing data passed in from other threads; it is signalled when there is work to do:

    Code:
    for ( ;; )
    {
    #if _WIN32
        WaitForSingleObject(_sync_event, INFINITE);
    #else
        sync_event_wait(&_sync_event);
    #endif
    
        if ( g_quitting )
            break;
    
        // do work
        ...
    }
    Since this thread is blocking, when the user wants the application to quit, it will be torn down dramatically (or at least, not in the method we desire, so it can properly clean up) - hence we signal the thread so it executes, where it then checks the global variable to see if the signal was a result of application closure.

    If it is, it breaks out of the loop, does its cleanup, and returns in short order. Consider I have 2-8 of these threads (but potentially many more).

    Is this one of those rare cases that a global variable is tentatively allowed?

    I've also considered using a singleton to maintain this, which has a method to access this; while it has the benefit that only a friend class can 'set' the quitting state, as far as I'm aware it's nothing more than a glorified global, with its own set of drawbacks:

    http://blogs.msdn.com/b/scottdensmor...25/140827.aspx
    http://misko.hevery.com/code-reviewe...te-singletons/

    I use a similar method for maintaining multiple connections to servers, which block on a read from the socket.

    What method would YOU use to perform such a case? There must be other applications out there with similar operations, but I want to use good practice, and wonder if there is a different way these can be designed?

  2. #2
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    If using WIN32 you can use multiple events. One event for normal work and one event for 'quitting'. So instead of using WaitForSingleObject you then use WaitForMultipleObjects(..) and check which object is signalled when the function returns.
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  3. #3
    Join Date
    Jul 2010
    Posts
    6

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    But I would then have to store this HANDLE in the same namespace as the boolean value though; so it wouldn't provide any benefit (if anything, it's more work).

    And yes, this is cross-platform - while not entirely related, here's the sync_event being used in the example above:

    Code:
    struct sync_event
    {
        pthread_mutex_t  mutex;
        pthread_cond_t   condition;
        unsigned         flag:2;
    };
    But as I said, not all of the threads are using this (some are blocking within socket reads).

  4. #4
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    1) there is nothing wrong with global variables
    2) static member functions in globally accessible classes are globals in disguise.
    3) you don't "need" globals (or singletons or ...) for this, you can pass the _sync_event and any other things you might otherwise make global into the threadfunctions...
    4) 3 might be awkward to implement, in which case rule 1 and 2 still apply.

  5. #5
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    1) there is nothing wrong with global variables
    But be careful about synchronisation etc etc etc when using global variables shared by multiple threads.
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  6. #6
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    Quote Originally Posted by 2kaud View Post
    But be careful about synchronisation etc etc etc when using global variables shared by multiple threads.
    This has nothing to do with them being global. The same issues will be present regardless of the methodoly used to make the threads have access to them.

    One of the prominent reasons why "globals are bad" is that it easily allows threads access without proper synchronisation. This doesn't go away with using global singleton classes (which is why they're equally bad it's just disguised as "more c++")


    Only with method 3 can you really prevent a thread from accessing anything that you didn't intend to be accessed. But you still need to provide proper synchronisation for anything you have intended to be accessed


    In the reality world of programming according to 1) and 2) however, sticking a comment on there with /* DO NOT USE FROM THREADS */ is often equally effective with a lot less programming hassle

  7. #7
    Join Date
    Jul 2010
    Posts
    6

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    Thanks for the clarification; it is as I suspected.

    I've seen the case of 'no globals whatsoever' result in some really nasty class interfacing & inheritance, which ends up making the code less legible/easy to follow, and without the benefits of locking anyway.

    It's similar to the 'no gotos' as well - I use them for error handling in the same way the linux kernel code does - jump to the end of the function where specific cleanup can be done for each failure scenario, while maintaining the original 'successful/expected code path'.

    A bit of a followup to the original query then; is the concept of something like this something that should be avoided, or makes sense/provides some protection while not interfering with OO style and practices (ignoring possible use of shared_ptr, singleton/global, etc.)?

    Code:
    // Runtime.h
    
    class Config;
    
    class Runtime
    {
    	friend void app_init();
    private:
    	Runtime operator = (Runtime const&);
    	Runtime(Runtime const&);
    	Runtime() : _quitting(0), _config(nullptr) {}
    	~Runtime();
    
    	bool		_quitting;
    
    	Config	*_config;
    
    	void expose_config(Config *cfg)
    	{
    		_config = cfg;
    	}
    public:
    	static Runtime& Instance()
    	{
    		static Runtime	rtime;
    		return rtime;
    	}
    
    	Config* GetCfg() const
    	{
    		return _config;
    	}
    
    	bool IsQuitting() const
    	{
    		return _quitting;
    	}
    };
    
    extern Runtime		&runtime;
    Code:
    // main.cpp
    
    void app_init()
    {
    	Config	*app_cfg;
    
    	app_cfg = new Config;
    	app_cfg->Load(...);
    
    	runtime.expose_config(app_cfg);
    	// expose other essentials...
    }
    
    int main(int argc, char **argv)
    {
    	app_init();
    	// catch
    
    	...
    
    	if ( runtime.GetCfg()->IsFooBar )
    		...
    
    	if ( !runtime.IsQuitting() )
    		// oh dear, shouldn't be quitting
    		...
    }

  8. #8
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    Quote Originally Posted by Septic View Post
    I've seen the case of 'no globals whatsoever' result in some really nasty class interfacing & inheritance, which ends up making the code less legible/easy to follow, and without the benefits of locking anyway.

    It's similar to the 'no gotos' as well - I use them for error handling in the same way the linux kernel code does - jump to the end of the function where specific cleanup can be done for each failure scenario, while maintaining the original 'successful/expected code path'.
    "Only when you know and understand the rules can you then break them"
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  9. #9
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    regarding your original use case, all threading libraries I know of allow you to pass data to the spawn thread; for example, given some synchronization object "mysync", in c++11 you can write things like

    Code:
    int main()
    {
    	std::shared_ptr<mysync> sync = ...;
    	std::thread thread( [=]{ /* ... use sync ... */ } ); // copying a shared_ptr is thread safe ( and lock-free )
    
    	// ... use sync ...
    where the data can be stored in the callable object directly, or passed as an argument to std::thread ( note that in both cases the callable and the arguments are copyed in the spawning thread and that the std::thread constructor synchronizes with the callable invokation ).

    That said, all this should be hidden behind some higher level abstraction I hope. In this regard, I fail to see how the choice of an "application quitting method" ( which is an implementation detail ) should affect a global design choice ...

  10. #10
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: Singleton vs Globals vs Alternatives (Best Practice, Design)

    Quote Originally Posted by Septic View Post
    Thanks for the clarification; it is as I suspected.

    I've seen the case of 'no globals whatsoever' result in some really nasty class interfacing & inheritance, which ends up making the code less legible/easy to follow, and without the benefits of locking anyway.

    It's similar to the 'no gotos' as well - I use them for error handling in the same way the linux kernel code does - jump to the end of the function where specific cleanup can be done for each failure scenario, while maintaining the original 'successful/expected code path'.

    A bit of a followup to the original query then; is the concept of something like this something that should be avoided, or makes sense/provides some protection while not interfering with OO style and practices (ignoring possible use of shared_ptr, singleton/global, etc.)?
    As a general rule. Prefer the "better design" in so far as that better design doesn't make you take "excessive" detours. What is "excessive", you'll have t decide that on your own. The problem with allowing globals, and goto's and other "nasties" in an otherwise "pure" design is that too often it ends up programmers getting used to the nasties (because they're easy) and applying them when inappropriate not feelign any guilt "because it's being used elsewhere anyway".

    This very thing was (it still may be, I haven't been there in a long time) present in the linux kernel code. Usage of goto where it was no longer appropriate "because it was used elsewhere anyway".

    Sticking with the better design nomatter what will cause you to spend more time on the project than you maybe can really afford to. There are always "real world issues" at play as well as "clean and pure c++ design".

    that said. With threads. I usually put all the "global/shared stuff" in a specific class (usually called CThreadData) and it has all the synchronisation baked into the methods of that class. Neither the main GUI thread nor worker threads can ever access the shared data directly without going through properly coded/tested synchronized methods.
    All you need to do is make a single instance of that class in your gui thread (or one per worker thread, depending on the approach), and when creating a worker thread you pass the pointer to the CThreadData along to the threadfunction.
    it's a clean design, and it's easy to implement (usually) it can sometimes get hairy though. by moving the sync issues out of the thread code and into a class of it's own, the thread functions tend to be easier to write/understand as well.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured