Custom allocator for std::map

Is the below code correct?
Or does the allocated type mismatch with what an std::map will try to allocate?

1
2
3
typedef std::allocator<std::pair<int, std::string>> MyAlloc;

std::map<int, std::string, std::less<int>, MyAlloc> m;

Is there a specific reason you are explicitly specifying the allocator in this case? (And technically it should be const int in the allocator pair)
Last edited on
I want to use a memory pool for better std::map performance.

I'm currently trying to decipher the members of std::allocator. Have you any tips?
I'm not sure what there is to decipher?
http://www.cplusplus.com/reference/memory/allocator/
http://en.cppreference.com/w/cpp/memory/allocator

Are you just trying to make sense of the implementation?
Thanks for the links, Cubbi.
I tried to implement my own allocator, but its performance is abysmal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class PoolAllocator: public std::allocator<std::pair<const KeyType, CodeType>> {

    class MemoryPool {
    public:

        std::stack<pointer, std::vector<pointer>> aa; // Available Addresses

        MemoryPool():
            bytes(new char[sizeof (CodeType) * globals::dms])
        {
            pointer begin       = reinterpret_cast<pointer> (bytes.get());
            const pointer end   = begin + globals::dms;

            while (begin != end)
                aa.push(begin++);
        }

    private:

        std::unique_ptr<char[]> bytes; // actual memory
    };

public:

    pointer allocate(size_type n, std::allocator<void>::const_pointer = 0)
    {
        if (n > max_size())
            throw std::invalid_argument("MemoryPool::allocate(), bad `n'");

        // memory pool depleted
        if (mp.aa.empty())
            throw std::bad_alloc();

        pointer p = mp.aa.top();
        mp.aa.pop();
        return p;
    }

    /// @warning Naive implementation, assumes `p` is valid.
    void deallocate(pointer p, size_type n)
    {
        if (n > max_size())
            throw std::invalid_argument("MemoryPool::deallocate(), bad `n'");

        // memory pool full
        if (mp.aa.size() == globals::dms)
            throw std::runtime_error("MemoryPool::deallocate(), `aa' is full");

        mp.aa.push(p);
    }

    size_type max_size() const
    {
        return 1;
    }

private:

    static MemoryPool mp;
};

the rebind member template is missing (and since it's derived from std::allocator (why), the c++11 automatic rebind won't help either)
Last edited on
Please give more detail, what's wrong with deriving from std::allocator?

Also is the missing rebind the cause of the slowness?
Deriving from concrete, non-polymorphic, classes is typically a bad idea. If a class doesn't have a virtual destructor, it wasn't designed with inheritance in mind (compare to inheritable standard library classes like streams, buffers, and facets)

In this case, because you don't provide rebind, parent class rebind is selected, and parent (std::allocator<K,V>) is used for all allocations done by a container that's instantiated with this PoolAllocator<K,V>.
closed account (zb0S216C)
I don't see any checks to ensure "PoolAllocator::bytes's" alignment. Most modern operating systems try to return aligned pointers, but this is not always the case. On some CPUs, attempting to read misaligned data can cause a complete failure of the CPU which requires a restart. Granted, the CPUs we have now are capable of handling misaligned addresses, but it's not without a performance penalty. If you want performance, align your data.

Based on what I've seen in the implementation of "std::malloc_allocator" and "std::new_allocator", neither assure alignment of the allocated memory, which leads me to believe that the implementers of the aforementioned classes assume the OS will align the memory for them.

In your implementation of "PoolAllocator::deallocate( )", why are you throwing exceptions left, right and centre? Throwing exceptions is expensive and you should only throw them when something really has gone wrong. I personally think you're misusing exceptions in your code.

Also, what's the purpose of "PoolAllocator::max_size( )"?

Wazzak
Last edited on
both malloc() and operator new() assure alignment.

malloc() (quoted from C99 7.20.3/1)
The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object


operator new() (quoted from C++11 ยง18.6.1.1[new.delete.single]/1)
The allocation function (3.7.4.1) called by a new-expression (5.3.4) to allocate size bytes of storage suitably aligned to represent any object of that size.

and in 3.7.4.1
The pointer returned shall be suitably aligned so that it can be converted
to a pointer of any complete object type with a fundamental alignment requirement
Last edited on
closed account (zb0S216C)
Actually, alignment is not always respected even though the standard says so due to compiler bugs. In addition, even if "new" and "malloc( )" do return aligned memory, it's not guaranteed to be sufficiently aligned for the program's needs; alignment to a CPU cache-line springs to mind.

Wazzak
Last edited on
Such fundamental bugs are rare, but need to be reported in any case.
Over-aligned types do need special care (and special allocators if they are placed in a container), but this isn't the case.
closed account (zb0S216C)
How do we know that Catfish's allocator is not a special case? We don't know what sort of data is going to be stored. In addition, the allocator needs respect special alignment requirements for types that it may be required to store at some point in the future if it [the allocator] has any chance of being flexible and reusable.

Wazzak
Last edited on
Topic archived. No new replies allowed.