Search code examples
c++namespacesglobal-variablesextern

C++ - namespaces with global variables and functions using extern


Namespaces containing global variables and functions

I am looking for some pointers to what can be considered "good practice" when working with modifiable global variables and functions inside a namespace and how these are accessed by different files and linked together.

Simple background

I have a program where I want to define a namespace containing global variables and functions that give a simple interface to modify said variables. I want this to be stored inside a header file which I can include in different parts of the program I am writing.

I am still a beginner when it comes to C++ and have previous experience using Java and static functions and variables. Earlier, I had everything declared inside a class with static member variables and functions. However, when it comes to using C++, namespaces seems to be the favoured way to go instead, so I've opted out from using it (might be wrong on that part, not sure).

rt_handler.h

The header file rt_handler.h that I am defining looks something like this:

namespace rt_handler
{
    std::unordered_map<function_name_t, InfoFunction> info_map;
    /* Other variables stored here*/

    void add_host_2_info_func(function_name_t function_name, InfoFunction info)
    {
        info_map.emplace(function_name, info);
    }
    /* Other functions */
}

Now, for what I want to accomplish is to be able to modify, for instance, info_map by making a call rt_handler::add_host_2_info_func(arg1, arg2);. For what I understand, I can use the extern keyword for functions/variables to say that a symbol is defined elsewhere.

My question is then, how would I use this in practice? say I have file A.cpp which includes the header, and another file B.cpp which is linked together with A.cpp and wants to call the functions and modify the same instance of the hash_map info_map. Does extern add_host_2_info_func(function_name_t, InfoFunction) at the top of B.cpp do the trick? Or is there some other way to go about it entirely?

Cheers!

Edit

Thank you all for the replies and input. I realise there were some fundamental issues with the initial approach I had to the design. I decided to revert my code back to what I had previously, a simple class, Rt_Handler declared in the header with implementation in the source file.

I now just store a shared_ptr<Rt_Handler> reference to the object inside one of my source files and have others refer to that instantiation.

What Weijun Zhou pointed out is more the design I was looking for - a header only implementation, which wasn't a pattern I was aware of. If there is any input on the header only implementations, I'm all ears, thanks!


Solution

  • Expanding on the comment by @marcinj, you only need to put the declarations of the functions in the header. The implementation details, including your map itself, should go to the cpp file.

    That is, in your header, simply write (include guards omitted)

    //rt_handler.h
    typedef /*...*/ function_name_t;
    typedef /*...*/ InfoFunction;
    namespace rt_handler
    {
        void add_host_2_info_func(function_name_t function_name, InfoFunction info);
        /* Other functions */
    }
    

    And in your implementation file (.cpp), put your variables in an anonymous namespace to avoid conflicts. You may choose to put the anonymous namespace inside your rt_handler namespace or leave it outside. Here I do the former.

    #include "rt_handler.h"
    namespace rt_handler
    {
        namespace
        {
            std::unordered_map<function_name_t, InfoFunction> info_map;
            /* Other variables stored here*/
        } //end of anonymous namespace
    
        void add_host_2_info_func(function_name_t function_name, InfoFunction info)
        {
            info_map.emplace(function_name, info);
        }
        /* Other functions */
    }
    

    This pattern will work as long as no templates are involved. If there are templates then you have to put the implementation in the header, and if a variable/function (not a variable/function template) is defined in the header then you need the inline keyword to avoid multiple definition.

    That is, assuming for some reason you absolutely need the declaration of the variable info_map in the header, then you can write the header as follows. The convention is to put implementation details that has to go into the header but should not really be exposed to the user into its own namespace named detail or alike. Note that anonymous namespace should not be used here.

    namespace rt_handler
    {
        namespace detail
        {
            inline std::unordered_map<function_name_t, InfoFunction> info_map;
            /* Other variables declared with inline */
            /* Other variable templates (for which the inline keyword is not needed) */
        }
    
        void add_host_2_info_func(function_name_t function_name, InfoFunction info);
        /* Other functions */
        /* Other function templates, some of which need to use the info_map variable */
    }
    

    Then in the implementation file, leave out the definition for info_map entirely.

    #include "rt_handler.h"
    namespace rt_handler
    {
        void add_host_2_info_func(function_name_t function_name, InfoFunction info)
        {
            info_map.emplace(function_name, info);
        }
        /* Other functions */
    }
    

    The extern keyword you mentioned can be used to prevent a declaration of variable from becoming a definition, which provides an alternative to inline. You should still put the name in a detail namespace if it is meant to be hidden from the user, though. If you decide to go with the extern way, the header should look like this,

    namespace rt_handler
    {
        namespace detail
        {
            extern std::unordered_map<function_name_t, InfoFunction> info_map; //Declaration, not definition
            /* Other variables declared with extern */
            /* Other variable templates */
        }
    
        void add_host_2_info_func(function_name_t function_name, InfoFunction info);
        /* Other functions */
        /* Other function templates, some of which need to use the info_map variable */
    }
    

    Then in the implementation file, you need to actually define the variable.

    #include "rt_handler.h"
    namespace rt_handler
    {
        namespace detail
        {
            std::unordered_map<function_name_t, InfoFunction> info_map;
            /* Other variables */
        } //end of detail namespace
    
        void add_host_2_info_func(function_name_t function_name, InfoFunction info)
        {
            info_map.emplace(function_name, info);
        }
        /* Other functions */
    }
    

    The first pattern is really what you should prefer if it is possible. The second is most often usedin implementing header-only libraries, in which case the functions should also be declared inline and the implementation would go to the header. The third is seen less often but still a valid choice.