Есть ли способ получить массив неинициализированных классов, которые не уничтожаются при вызове delete []? - PullRequest
0 голосов
/ 12 июня 2019

Я реализую пул объектов с типом стирания, чтобы пулы могли храниться в коллекциях и могли хранить другие пулы. Функциональность есть, но также имеет утечку памяти, вызванную не удалением самого пула. У меня есть следующий код:

template <typename T>
struct ObjectPool::PoolModel final : PoolConcept {
    PoolModel(uint size) : pool( new T[size](), [](T _[]){ /*Problem!*/}) {}
    virtual ~PoolModel() {}
private:
    std::unique_ptr<T[], std::function<void(T[])>> pool;
};

Прилично, "Проблема!" где моя проблема Вы можете задаться вопросом, почему я заменил удаление по умолчанию для uinique_ptr тем, которое ничего не делает. Это связано с тем, что пул заполняется полностью фиктивными данными во время уничтожения, и поэтому при уничтожении пула пулов (или любого пула объектов, содержащих интеллектуальные указатели или другие объекты с деструкторами), удаление массива вызовет деструктор каждого класса и выполните удаление фальшивого умного указателя, который вызывает ошибку. Таким образом, я заменил noop-деструктор, и все работает отлично.

Отсюда и утечка памяти. Я избавился от удаления по умолчанию, поэтому каждый объект пула оставляет свой пул. Я попытался ":: оператор delete [] (arr);" в "Проблеме!" spot, так как он предположительно удаляет массив без вызова деструкторов объекта, но это приводит к «munmap_chunk (): неверный указатель». Я пытаюсь найти способ C ++ сделать это и не нужно прибегать к malloc и бесплатно. Кроме того, я хотел бы знать, есть ли способ выделить массив в первую очередь, не вызывая конструктор по умолчанию для каждого элемента массива, и просто оставить их неинициализированными.

1 Ответ

3 голосов
/ 12 июня 2019

Массив T s всегда должен быть заполнен действительными T объектами. Исключений нет.

Решение ваших проблем - не использовать массив объектов T. Вместо этого выделите неинициализированное хранилище с правильным размером и выравниванием для n T объектов и инициализируйте их с помощью Placement-New. Затем вы будете отвечать за ручной вызов деструкторов T объектов. Это решение std::vector и аналогичные структуры данных используют:

template <typename T>
struct ObjectPool::PoolModel final : PoolConcept {
    using StorageT = std::aligned_storage_t<sizeof(T), alignof(T)>;
    PoolModel(uint capacity)
       : pool{ std::make_unique<StorageT[]>(capacity) },
         size{0},
         capacity{capacity}
    {}

    virtual ~PoolModel() {
        for (size_t i = 0; i < size; ++i) {
            T* ptr = std::launder(reinterpret_cast<T*>(&pool[i]));
            ptr->~T();
        }
    }

    void insert(T obj) {
        assert(capacity > size);
        new (&pool[size + 1]) T{std::move(obj)};
        ++size;
    }

private:
    std::unique_ptr<StorageT[]> pool;
    uint size;
    uint capacity;
};

Конечно, поскольку вы в основном заново изобретаете std::vector, вы можете просто использовать std::vector вместо:

template <typename T>
struct ObjectPool::PoolModel final : PoolConcept {
    PoolModel(uint capacity) {
        pool.reserve(capacity);
    }

    virtual ~PoolModel() {}

    void insert(T obj) {
        pool.push_back(std::move(obj));
    }

private:
    std::vector<T> pool;
};
...