Reputation: 5591
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
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