K
K
kruta2020-06-11 10:05:24
C++ / C#
kruta, 2020-06-11 10:05:24

What's wrong with unordered_map working?

There is an unordered_map (char*, int, HashFunc, MyEqualTo, CustomAllocator>.
I don’t understand how it works: when initializing, a CustomAllocator is created, then for some reason the copy constructor of the allocator is called. Buffers are copied by the memcpy function

As a result, a destructor is called in some allocator , all data is deleted (including maps allocated for the needs), an exception flies in the xmemory file.

In general, I got confused. Explain what it is .

#pragma once
#include <vector>


template<class T>
class CustomAllocator
{
public:

  using value_type = T;
  using pointer = T*;
  std::vector<void*>* buffers;
  void* currentBuffer;
  int bufferSize;
  size_t bufferPosition;


  CustomAllocator() noexcept
  {
    bufferSize = 1048576;
    buffers = new std::vector<void*>();
    allocateNewBuffer();
  }

  ~CustomAllocator()
  {
    for (int i = 0; i < buffers->size(); ++i)
      if (buffers->at(i) != nullptr)
        delete buffers->at(i);
    delete buffers;
  }

  //CustomAllocator(CustomAllocator& other) :
  //	bufferSize(other.bufferSize), buffers(other.buffers),
  //	currentBuffer(other.currentBuffer), bufferPosition(other.bufferPosition)
  //{
  //	other.buffers = nullptr;
  //	other.currentBuffer = nullptr;
  //}

  ////template<typename T1>
  ////CustomAllocator(CustomAllocator<T1> other) :
  ////	bufferSize(other.bufferSize), buffers(other.buffers),
  ////	currentBuffer(other.currentBuffer), bufferPosition(other.bufferPosition)
  ////{
  ////	other.buffers = nullptr;
  ////	other.currentBuffer = nullptr;
  ////}


  pointer allocate(std::size_t size)
  {
    if (bufferPosition + size * sizeof(T) >= bufferSize)
      allocateNewBuffer();
    T* pointer = ((T*)currentBuffer + bufferPosition);
    bufferPosition += size * sizeof(T);
    return pointer;
  }

  void deallocate(void* p, std::size_t size)
  {
    bufferPosition -= size;
  }
private:

  void allocateNewBuffer()
  {
    currentBuffer = malloc(bufferSize);
    buffers->push_back(currentBuffer);
    bufferPosition = 0;
  }
};

UPD:
the problem was that copies of the allocator were created, which were then destroyed along with the buffer, to which it was allocated for data. As a result, there was an access to the memory that was freed. I never fully understood why copies were needed

Answer the question

In order to leave comments, you need to log in

1 answer(s)
E
Evgeny Shatunov, 2020-06-11
@kruta

The point is that a container, or smart pointer, in modern C++ can ( and does ) change the allocated type of a template allocator several times. In this case, the original allocator passed through the container constructor will be used to construct a new allocator with a new type. This means that the new allocator is not constructed via the copy constructor, but via the conversion constructor.
If the allocator template does not specify the allocator type change rule ( A::template rebind<U>::other), by default, when the allocator type is changed, the first template parameter will be replaced. Was with us Alloc<Foo, ...>, will be Alloc<Bar, ...>.
This means that in order to correctly pass the state of the allocator, you need to provide a conversion constructor from the allocator from your template, but with a different first argument.
You can also use Polymorphic Allocator , but this will require changing the standard to C++17.
This strategy is not easy to describe, so I will resort to links to reports on this topic.
CppCon 2017: Bob Steagall “How to Write a Custom A...
051. Modern C++ Allocators – Ruslan Harutyunyan (Intel)
Taming dynamic memory - An introduction to custom ...
C++Now 2018: David Sankel “C++17's std::pmr Comes ...
This, of course, is far from everything on this topic. But I don't have a goal to write everything down. I gave links that I trust in terms of the purity of information.
I advise you to just go through habr and ytube by searching for reports and articles.
To put it very briefly, a polymorphic allocator allows you to create allocators with a state and a more convenient interface, but working with such allocators will involve some cost of accessing the allocator. The cost of accessing a standard allocator, in this regard, can usually be neglected.
But if you work with the standard before C++11, then your allocator can not have a state at all.

All custom allocators must also be stateless. (until C++11)

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question