0

I'm having trouble getting some c++ templates to work. Here's what I want to do:

template <class T, const int I = 16, const int N = 65536, const int M = 2>
class pool
{
public:
  pool();
  ~pool();

  void clear();

  T * get();
  void put(void * p);
};


template<class T, const int I = 16, const int N = 65536, const int M = 2>
class pool_of_pools {
private:
  static std::map<pthread_t, pool<T,I,N,M>*> pools;
public:
  typedef typename std::map<pthread_t, pool<T,I,N,M>*>::iterator iterator_type;

  static void registerThread(pthread_t id) {
    if (pools.find(id) == pools.end())
      pools[id] = new pool<T,I,N,M>();
  }

  static void registerThread() {
    registerThread(pthread_self());
  }

  static void clear() {
    for (iterator_type it = pools.begin(); it != pools.end(); ++it)
      it->second->clear();
  }

  static pool<T,I,N,M>* getPool() {
    if (pools.find(pthread_self()) == pools.end())  // <-- warning here
      registerThread();
            
    return pools[pthread_self()];
  }
};

The idea is to have a pool of pools where the current thread always allocates from it's own pool but any thread can release the objects and they will go back to the correct pool. A pointer to their origin pool is stored within the object itself. When it runs, worker threads will allocate objects but a single collector thread will free them all when done.

Notice the static std::map. When I try to instantiate that as:

template<typename T, int I, int N, int M> std::map<pthread_t, pool<T,I,N,M>*> pool_of_pools<T,I,N,M>::pools;

this gives me a warning of instantiation of variable 'pool_of_pools<int>::pools' required here, but no definition is available. It compiles and runs, but when different threads call getPool, they seem to register with their own thread-local(?) versions of pools. I create a thread, then call registerThread(id) with the thread id, and also registerThread() to register the main thread. But when I call getPool from the created thread, the address of pools (&pools) is different than what it was when I registered the two threads from the main thread. I'm not sure how that's possible since it's static and a global.

When I try to instantiate the map as:

template<> std::map<pthread_t, void*> pool_of_pools<int,512*1024,1024*1024,2>::pools;

it won't compile. I get a linker error undefined reference to pool_of_pools<int, 52488, 1048576, 2>::pools

I've done single templated classes before but never a templated class that has a static templated class instance in it.

How do I do this? Is it even possible?

M. Da
  • 61
  • 1
  • 9
  • The problem seems to be https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file?r=Saves_AllUserSaves – πάντα ῥεῖ Jul 17 '23 at 03:05

1 Answers1

3

The static needs to be defined somewhere for any instantiation of the class template. A way around this is to encapsulate your static data in a function. This does mean that it's not constructed until something uses it, but that probably isn't a big issue for you.

So, instead of using pools everywhere, turn that into the function pools() which owns the static. Provided you're using at least C++11, the standard guarantees thread-safety which protects initialization of the static data.

static std::map<pthread_t, pool<T,I,N,M>*>& pools()
{
  static std::map<pthread_t, pool<T,I,N,M>*> s_pools;
  return s_pools;
}

Then it's just a matter of changing everywhere that you use the identifier pools to instead call the function pools().

Full code with these changes:

template<class T, const int I = 16, const int N = 65536, const int M = 2>
class pool_of_pools {
private:
  static std::map<pthread_t, pool<T,I,N,M>*>& pools()
  {
    static std::map<pthread_t, pool<T,I,N,M>*> s_pools;
    return s_pools;
  }

public:
  typedef typename std::map<pthread_t, pool<T,I,N,M>*>::iterator iterator_type;

  static void registerThread(pthread_t id) {
    if (pools().find(id) == pools().end())
      pools()[id] = new pool<T,I,N,M>();
  }

  static void registerThread() {
    registerThread(pthread_self());
  }

  static void clear() {
    for (iterator_type it = pools().begin(); it != pools().end(); ++it)
      it->second->clear();
  }

  static pool<T,I,N,M>* getPool() {
    if (pools().find(pthread_self()) == pools().end())  // <-- warning here
      registerThread();
            
    return pools()[pthread_self()];
  }
};
paddy
  • 60,864
  • 6
  • 61
  • 103
  • 2
    Your an angel! Thank you so much, this has been driving me crazy. And the static function `pools()` that owns that static map is so much nicer than what I've done before. – M. Da Jul 17 '23 at 03:36
  • 1
    There is some good reading (plus some external links), on the technique in [this Stack Overflow answer](https://stackoverflow.com/a/1008289/1553090) – paddy Jul 17 '23 at 03:59