Search code examples
c++staticshared-ptrfunctor

Static const global functor instances


What is the best way to declare global instances of a function object so I can import and use the instances as callables throughout my program?

Specifically, I have created a template class which serves as a custom deleter for a shared pointer. Several pointer types in a third party library need to be deleted with a "Free" function that takes a reference to the pointer. An instance of the class is instantiated with the type of the pointer to delete, and a pointer to a function with the signature of the Free function. I am declaring the instances as const because there is no need for the member function pointer to ever change.

template <class T>
class Deleter {
public:
  typedef typename bool(CALLING_CONVENTION *DeleterFunc)(T**);
  Deleter(DeleterFunc deleter) : deleter_(deleter) {}
  void operator() (T* t) { if (t) { deleter_(&t) }

private:
  DeleterFunc deleter_;
};

static const Deleter<I_x> x_deleter(FreeXInterface);

My first attempt was a create instances for each of the pointer types in the .h file, but this resulted in multiply defined symbols if I include this header file in other code. So I changed the declaration of the instances to "static" and this compiles and seems to work fine, but I have seen warnings that this is not a good idea (especially if the objects belong to a namespace) because static is causing the linkage to be file-only, and that each compilation unit will have its own copy.

My question is does this matter if I don't really care if they are the same instance between files? I am not using this as global data, and the function objects don't really have any state. Would I have any concerns about threading if these objects are declared static? Is there a better way to implement this without using the static keyword?


Solution

  • Well, the basic answer to your linker question is here: How do I use extern to share variables between source files? Let's try to do a little better, though: You currently have to refer to the deletion function every time you instantiate a function, instead of having it chosen automatically based on the type.

    Of course we often pick types based on types: std::vector<int> is the dynamic array which is capable of holding ints. Linkage isn't a huge problem there: Everything's more or less inlined, each translation unit which mentions std::vector<int>::push_back() gets its own copy of the object code to put an int into a vector of ints, and the linker (sometimes) helps out by removing duplicated instantiations. But here we want an object, not a type.

    So what's halfway between templates and global objects? Static members of templated classes! Check it:

    // Deleter.h
    
    typedef typename bool(CALLING_CONVENTION *DeleterFunc)(T**);
    
    template <class T>
    class Deleter {
    public:
        static DeleterFunc s_deleterFunc;
    
        void operator() (T* t) {
            if(t) { s_deleterFunc(&t); }
        }
    };
    

     

    // XDeleter.cpp
    
    #include "Deleter.h"
    template<>
    DeleterFunc Deleter<I_x>::s_deleterFunc = FreeXInterface;
    

     

    // YDeleter.cpp
    
    #include "Deleter.h"
    template<>
    DeleterFunc Deleter<I_y>::s_deleterFunc = FreeYInterface;
    

    For each type you want a deleter for, you just provide the instantiation of the static member for the specialized class. The code which calls Deleter<I_x>::operator() only needs to include Deleter.h; the linker will take care of matching it to the one declared in XDeleter.cpp.

    Note that I'm doing it with templated classes, rather than templated functions, only because it allows you to use your function pointers. But you could instead do something as simple as:

    // Deleter.h
    
    template <class T>
    invokeDeleter(T* t);
    

     

    // XDeleter.cpp
    
    #include "Deleter.h"
    template<>
    invokeDeleter<I_x>(I_x* x)
    {
        ...
    }
    

     

    // YDeleter.cpp
    
    #include "Deleter.h"
    template<>
    invokeDeleter<I_y>(I_y* y)
    {
        ...
    }