Сбой программы выделения пула C ++ только при закрытии консоли - PullRequest
0 голосов
/ 25 февраля 2019

Я смотрю на эту реализацию пула .Я на самом деле немного его изменил, и мой полный код:

template <class T, size_t T_per_page = 200>
class PoolAllocator
{
    private:
        const size_t pool_size = T_per_page * sizeof(T);
        std::vector<T *> pools;
        size_t count;
        size_t next_pos;

        void alloc_pool() {
            next_pos = 0;
            void *temp = operator new(pool_size);
            pools.push_back(static_cast<T *>(temp));
        }
    public:
        PoolAllocator() {
            count = 0;
            alloc_pool();
        }

        void* allocate() {
            if (next_pos == T_per_page)
                alloc_pool();

            void* ret = pools.back() + next_pos;
            ++next_pos;
            ++count;
            return ret;
        }

        size_t getSize() const
        {
            return T_per_page * (pools.size() - 1) + next_pos;
        }

        size_t getCount() const
        {
            return count;
        }

        size_t getCapacity() const
        {
            return T_per_page * pools.size();
        }

        T* get(size_t index) const
        {
            if (index >= getCount()) { return NULL; }

            size_t poolIndex = index / T_per_page;
            return pools[poolIndex] + (index % T_per_page);
        }

        ~PoolAllocator() {
            std::cout << "POOL ALLOCATOR DESTRUCTOR CALLED" << std::endl;
            while (!pools.empty()) {
                T *p = pools.back();
                size_t start = T_per_page;
                if (pools.size() == 1){
                    start = next_pos;
                }

                std::cout << "start: " << start << std::endl;
                for (size_t pos = start; pos > 0; --pos)
                {
                    std::cout << "pos: " << pos << std::endl;
                    p[pos - 1].~T();
                }
                operator delete(static_cast<void *>(p));
                pools.pop_back();
            }
        }
};

template<class T>
PoolAllocator<T>& getAllocator()
{
    static PoolAllocator<T> allocator;
    return allocator;
}

class Node
{
    private:
        int id;
        std::vector<float> vertices;

    public:
        Node() : id(42)
        { 
            std::cout << "Node constructor called" << std::endl;
        }
        ~Node(){ std::cout << "Node destructor called" << std::endl; }

        void* operator new(size_t size)
        {
            std::cout << "Node operator new called" << std::endl;
            return getAllocator<Node>().allocate();
        }

        void operator delete(void*)
        {
            std::cout << "Node operator delete called" << std::endl;
        }
    };

int _tmain(int argc, _TCHAR* argv[])
{
    Node* n1 = new Node();
    Node* n2 = new Node();  
    Node* n3 = new Node();
    Node* n4 = new Node();

    std::cout << "Count: " << getAllocator<Node>().getCount() << " size: " << getAllocator<Node>().getSize() << " capacity: " << getAllocator<Node>().getCapacity() << std::endl;

    while (true){}

    return 0;
}

Когда я запускаю этот код в Visual Studio, он работает правильно, пока я не закрою консоль, и в этот момент я получаю ошибку нарушения доступа,Я попытался вручную вызвать деструктор на распределителе, и он, кажется, работает должным образом, но я должен где-то сделать ошибку.Я получаю ошибку:

enter image description here

Может кто-нибудь определить, где я делаю свою ошибку?

Редактировать 1:

При дальнейшем расследовании он все равно рухнет даже без новых линий Node в main.Кажется, связано с методом getAllocator () и как, возможно, вызывается деструктор?Или тот факт, что распределитель статичен ??

Редактировать 2:

Я на самом деле не думаю, что это имеет какое-либо отношение к моему распределителю!Если я попробую код:

class Node2
{
    private:
        int x;
    public:
        Node2():x(42){std::cout << "Node2 constructor called" << std::endl;};
        Node2(const Node2& other){ std::cout << "Node2 copy constructor called" << std::endl; };
        ~Node2(){ std::cout << "Node2 destructor called" << std::endl; };
};

Node2& Test(){
    static Node2 myIndex;

    return myIndex;
}

int _tmain(int argc, _TCHAR* argv[])
{
    Test();

    while (true){}

    return 0;
}

Это приведет к той же ошибке!Сюжет утолщается.Я предполагал, что новичок в написании пользовательских распределителей, что проблема была в коде распределителя.Я до сих пор не уверен, почему именно эта ошибка происходит для моего меньшего кода ...

Ответы [ 2 ]

0 голосов
/ 25 февраля 2019

Я могу заметить некоторые проблемы с этим распределителем пула.

  1. PoolAllocator владеет ресурсами, но нет ни специального конструктора копирования, ни назначения.Скорее всего, вы должны объявить их удаленными.И предоставить Move-конструктор и Move-назначение.Хотя это и не является фактором в этом конкретном примере, он может защитить вас от непреднамеренного возврата распределителя по значению.

  2. Функция alloc_pool() сбрасывает next_pos до выделения нового фрагмента.Исключение, если оно будет выдано operator new, оставит пул в несогласованном состоянии.

  3. Аналогично, исключение в pools.push_back() приведет к утечке нового фрагмента.Я верю, что std::vector<std::vector<std::byte>> будет делать правильно, что с современными векторами, подвижными.Но если вы абсолютно хотите использовать вектор необработанных указателей, вам следует зарезервировать дополнительное пространство в pools, затем выделить новый фрагмент, и только затем вызвать push_back и изменить состояние.

  4. Конструктор PoolAllocator может бросить без уважительной причины.Так как метод allocate() должен вызывать alloc_pool(), зачем вызывать его в конструкторе?Вы можете получить простой конструктор noexcept, просто оставив всю работу по выделению для allocate().

  5. PoolAllocator::allocate() и PoolAllocator::~PoolAllocator() не являются симметричными.Первый возвращает необработанную память без инициализированного объекта, а второй предполагает, что в каждом выделенном слоте есть правильно сконструированный объект.Это предположение опасно и очень хрупко.Представьте, например, что T::T() throws.

  6. Кажется, что getSize () и getCount () всегда возвращают одно и то же значение.Предназначено ли это?

  7. Деструктор удалит next_pos объекты в первом пуле, pools[0] и T_per_page объектах в каждом другом пуле.Но он должен удалить next_pos объектов в последнем пуле.

  8. Вас ждут замечательные ошибки, если T:~T() вызван из деструктора пула, который когда-либо пыталсявыделить другой объект из того же пула.Такой сценарий может показаться странным, но технически это может произойти.Destructor лучше поменять текущее состояние пула на локальные переменные и работать с ними.Повторяя при необходимости.

  9. Этот бесконечный цикл в main () может испортить уничтожение глобальных объектов.Компилятор может быть достаточно умен, чтобы выяснить, что return недоступен, и пропустить часть уничтожения.

  10. pool_size может быть статическим элементом.

0 голосов
/ 25 февраля 2019

Пишу ответ, так как я не могу комментировать вопрос.

Я не могу обнаружить очевидную ошибку в последнем коде.Вы уверены, что компилируете нужный файл, а не старую несохраненную версию и все такое?

Вы можете попробовать удалить строку

while (true){}

И позволить программе просто нормально завершиться.

Также вы можете попытаться запустить ваш код в режиме отладки, пошагово выполнив инструкции, чтобы найти тот, который вызывает проблемы.

...