Bruce Adams
Bruce Adams

Reputation: 5591

Convert a class wrapping a container to use template parameters for allocation

I have some classes which wrap STL containers

For example:

class Foo
{
private:
   class Bar
   {
    // stuff
   };

   using Container = std::vector<Bar>;

   Container contents;
};

I now find myself wanting to use custom allocators with Foo. How should I refactor the class without significantly increasing complexity or breaking encapsulation?

I must support platforms using compilers that do not yet support c++17 so I cannot use std::polymorphic_allocator (unless I use boost)

This is essentially the same question as:

Should I pass allocator as a function parameter? (my misunderstanding about allocator)

But I found that question unclear. It gives the answer as use a template parameter but using this I struggled to make it work.


What I would like to write is something like (pseudo-code not valid C++):

template<typename X = std::allocator>
class Foo
{
    class Bar
    {
    };
    using Container = std::vector<Bar, X<Bar> >;

private:
    Container<Allocator> contents;
};

Upvotes: 0

Views: 160

Answers (1)

Bruce Adams
Bruce Adams

Reputation: 5591

An allocator can be given as a template template parameter

The resulting template should look something like this:

template<template <typename T> typename ALLOCATOR  = std::allocator>
class Foo
{
    class Bar
    {
    // stuff
    };

    using Allocator = ALLOCATOR<Bar>;
    using Container = std::vector<Bar, Allocator >;

    Foo(const Allocator& allocator = Allocator{}):
       contents(allocator)
    {
    }
private:
    Container<Allocator> contents;
};

You can now use it with std::allocator as:

Foo<> asBefore;

or with your own allocator as:

MyAllocator alloc(params); 
Foo<MyAllocator> foo(alloc);

You might be able to wrap your Container class using a trick like the one given here so that you don't have to convert the whole class to a template. This would let you keep your encapsulation. I have not attempted this here.


Beware that:

Foo<MyAllocator> foo(MyAllocator(params)); //BAD

Is a somewhat vexing parse which generally won't work.

Also note that older compilers such as gcc 4.8 on RHEL7 do not allow typename for template template parameters so you must use class instead. See How can I use templete template parameters in older C++ compilers?

That is:

template<template <class T> class ALLOCATOR  = std::allocator>
class Foo
...

For completeness, if you do have C++17 or later, another alternative is polymorphic allocators. These potentially add the overhead of a virtual function call to every allocation but this is often less significant than the cost of a malloc. In the right circumstances the compiler may be able to eliminate this completely.

See for example polymorphic_allocator: when and why should I use it?

In this case your class wouldn't have to be converted into a template which may help with encapsulation.

class Foo
{
    class Bar
    {
    // stuff
    };

    using Container = std::pmr::vector<Bar>;

    Foo(const std::polymorphic_allocator& allocator = std::pmr::get_default_resource()):
       contents(allocator)
    {
    }
private:
    Container<Allocator> contents;
};

Note that std::pmr::vector<Bar> is basically shorthand for std::vector<Bar, std::pmr::polymorphic_allocator<Bar> >;

Upvotes: 1

Related Questions