6

I'm writing an allocator with a reference to another instance of some class which tracks the number of allocated bytes.

below is a minimal example of what I'm trying to do (adapted from here), just without the whole memory tracking class, instead I made a reference to some int which collects the bytes which got allocated so far. This reference gets assigned inside main and should be passed to the CustomAllocator:

#include <limits>   // numeric_limits
#include <iostream>
#include <typeinfo> // typeid

// container
#include <vector>
#include <list>
#include <forward_list>

template<typename T>
class CustomAllocator {
public:
    // type definitions
    typedef T value_type; /** Element type */
    typedef T* pointer; /** Pointer to element */
    typedef T& reference; /** Reference to element */
    typedef const T* const_pointer; /** Pointer to constant element */
    typedef const T& const_reference; /** Reference to constant element */
    typedef std::size_t size_type; /** Quantities of elements */
    typedef std::ptrdiff_t difference_type; /** Difference between two pointers */

    template<typename U>
    struct rebind {
        typedef CustomAllocator<U> other;
    };

    // return maximum number of elements that can be allocated
    size_type max_size () const throw() {
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }

    CustomAllocator(std::size_t& memAllocated) :
            m_totalMemAllocated(memAllocated) {
        std::cout << "construct " << typeid(T).name() << std::endl;
    }

    CustomAllocator(const CustomAllocator& src) :
            m_totalMemAllocated(src.m_totalMemAllocated) {
        std::cout << "copy construct " << typeid(T).name() << std::endl;
    }

    template<class U>
    CustomAllocator(const CustomAllocator<U>& src) :
            m_totalMemAllocated(src.getTotalMemAllocated()) {
    }

    // allocate but don't initialize num elements of type T
    pointer allocate(size_type num, const void* = 0) {
        m_totalMemAllocated += num * sizeof(T);
        // print message and allocate memory with global new
        std::cout << "allocate " << num << " element(s)" << " of size "
                << sizeof(T) << std::endl;
        pointer ret = (pointer) (::operator new(num * sizeof(T)));
        std::cout << " allocated at: " << (void*) ret << std::endl;
        return ret;
    }

    // deallocate storage p of deleted elements
    void deallocate(pointer p, size_type num) {
        m_totalMemAllocated -= num * sizeof(T);
        // print message and deallocate memory with global delete
        std::cout << "deallocate " << num << " element(s)" << " of size "
                << sizeof(T) << " at: " << (void*) p << std::endl;
        ::operator delete((void*) p);
    }




    // initialize elements of allocated storage p with value value
    // no need to call rebind with this variadic template anymore in C++11
    template<typename _U, typename ... _Args>
    void construct(_U* p, _Args&&... args) {
        ::new ((void *) p) _U(std::forward<_Args>(args)...);
    }

    // destroy elements of initialized storage p
    template<typename _U>
    void destroy(_U* p) {
        p->~_U();
    }

    // return address of values
    pointer address (reference value) const {
        return &value;
    }
    const_pointer address (const_reference value) const {
        return &value;
    }

private:
    std::size_t& m_totalMemAllocated;
};

template<typename T, typename U>
bool operator==(const CustomAllocator<T> a, const CustomAllocator<U>& b) {
    return true;
}

template<typename T, typename U>
bool operator!=(const CustomAllocator<T>& a, const CustomAllocator<U>& b) {
    return false;
}

int main() {

    std::size_t memAllocated = 0;

    CustomAllocator<int> allocatorInstance(memAllocated);

    std::vector<int> foo(allocatorInstance);
    foo.push_back(23);
    foo.push_back(12);
    foo.push_back(8);

    std::cout << "---" << std::endl;

    // here the same
    std::list<double> bar(allocatorInstance);
    bar.push_back(3.44);
    bar.push_back(1.18);
    bar.push_back(2.25);

    std::cout << "---" << std::endl;

    // debug output
    for (auto x : foo)
        std::cout << x << " ";
    for (auto x : bar)
        std::cout << x << " ";

    std::cout << "\nalloc_count: " << memAllocated << std::endl;

    std::cout << '\n';
    return 0;
}

My Problem here is that I don't know how to pass the exact same state (in the example m_totalMemAllocated) of an allocator instance to the other two containers (here: foo and bar). Since the standard says that C++11 allocators can have a state.

Update:

thank you for the answers so far :)

I know that you usually pass CustomAllocators as a template argument to the std containers; like this:

std::vector<int, CustomAllocator<int> > foo;
std::list<double, CustomAllocator<double> > bar;

also see: Difference between allocator supplied as template parameter and allocator supplied as constructor argument in C++ containers?

but here I do have a state which I'm not able to pass along and the default constructor would get called which I can't use unless I give the reference some default value (but that's not what I want either).

Putting the

std::size_t memAllocated = 0;

from main into the global scope would mean that all the containers which use the CustomAllocator would end up using the globally defined memAllocated. But I want to extend it so I can have some additional memory or instance memAllocated2 which then again gets assigned to some other allocator instances.

Side-Note:

For a stateful version of allocators for different containers than STD-Containers, see

How to track memory usage using EASTL?

Robert
  • 127
  • 1
  • 10
  • 1
    A simple way would be to make each allocator instance be "stateless" and have the type itself hold a global state across instances. – KABoissonneault Apr 05 '17 at 14:24
  • Errr... presumably you don't mean `vector`, but `vector>`? – Kerrek SB Apr 05 '17 at 14:25
  • 1
    Using this allocator model, the allocator instances are required to be stateless. However, in C++17 we can use versions of the containers in the `std::pmr` namespace, that support [***p**olymorphic **m**emory **r**esources*](http://en.cppreference.com/w/cpp/memory/polymorphic_allocator). – BoBTFish Apr 05 '17 at 14:27
  • 2
    You should pass *both* the type of the allocator as a template parameter, *and* the instance to the constructor. As in `std::vector > foo(allocatorInstance);` – Igor Tandetnik Apr 05 '17 at 15:04
  • @IgorTandetnik You are right! I forgot to give the container the instance of its allocator thank you :) . There seem to be several mistakes in my codepiece (like passing the allocatorInstance without passing the template type to the contianer). But anyhow. Why is the allocator instance needed when the std allocator does not have a state? – Robert Apr 06 '17 at 08:01
  • Should the state be shared among all allocators and even among allocators for different type (say `CustomAllocator` and `CustomAllocator`)? – Serge Ballesta Apr 06 '17 at 08:38
  • @SergeBallesta yes the state in this case the reference to memAllocated (inside main) should be shared among all allocators. Is that possible or impossible with this solution, since the container classes use rebind to get the allocator of type of its inner nodes, which doesn't use any copy of the state? – Robert Apr 06 '17 at 09:07
  • I'm very confused by your question. `std::allocator` doesn't have state, and so you don't normally pass an instance of it to a container; the container then default-constructs one, and it's as good as any other. But `CustomAllocator` does have state - that's the whole point of exercise, after all - and moreover doesn't even provide a default constructor. So of course you need to pass an instance of it to a container. This all seems quite obvious, I don't understand which part you find unclear. – Igor Tandetnik Apr 06 '17 at 12:59
  • *"the container classes use rebind to get the allocator of type of its inner nodes, which doesn't use any copy of the state"* What do you mean? How else can a container possibly construct an instance of `CustomAllocator`, if not as a copy of `CustomAllocator` instance is was given? There aren't any other constructors it could possibly use. – Igor Tandetnik Apr 06 '17 at 13:01
  • @IgorTandetnik thank you for your clarifications. You are right, list needs to be using the templatized copy constructor to build a constructor of its inner type U. Rebind just seems to be used in case of the list for getting the allocator type of _List_node and int itself. – Robert Apr 06 '17 at 13:42

1 Answers1

3

To make sure that the state is shared among all allocators instances, the first idea would to make it a static member. But that alone would not be enough, because different template instanciations are indeed different types and each would have its own copy of the static member. So I can imagine only 2 ways: make the state an auxilliary class containing only static members, or use a singleton pattern:

Static members of auxilliary class:

struct CustomAllocatorState {
    static std::size_t& m_totalMemAllocated;
}
std::size_t& CustomAllocatorState::m_totalMemAllocated = 0; # do not forget definition...

template<typename T>
class CustomAllocator {
public:
    ...
    pointer allocate(size_type num, const void* = 0) {
        CustomAllocatorState::m_totalMemAllocated += num * sizeof(T);
        ...

Singleton pattern (you could use any other C++ singleton pattern, this one is damned simple but does not resist to the static initialization fiasco):

class CustomAllocatorState {
    CustomAllocatorState(): m_val(0) {}
    static CustomAllocatorState state;

public:
    int m_val;
    static CustomAllocatorState& getState() {
        return state;
    }
};
CustomAllocatorState CustomAllocatorState::state;

template<typename T>
class CustomAllocator {
public:
    ...
    CustomAllocator() :
        state(CustomAllocatorState::getState()) {
            std::cout << "construct " << typeid(T).name() << std::endl;
    }
    ...
    pointer allocate(size_type num, const void* = 0) {
        state.m_totalMemAllocated += num * sizeof(T);
        ...
private:
    CustomAllocatorState& state;
};

Static members in an auxilliary class is probably simpler, but if you already use a singleton pattern in your application, it can make sense to use it here too.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252