Почему мой код не работает с пользовательским распределителем? - PullRequest
2 голосов
/ 08 октября 2019

В чем проблема и как ее исправить? Не пытаясь сжать пользовательский распределитель, мой вектор, на первый взгляд, работает правильно. Я также буду рад указать на любые ошибки в моем коде. Или пример правильной реализации пользовательского вектора или другого контейнера, который мне поможет. Этот код не работает:

using MyLib::Vector;
int main()
{
    //Vector<int> v;    //Works fine
    Vector<int> v(CustomAllocator<int>());
    for (size_t i = 0; i < 256; i++) {
        v.push_back(i);    //Error: "expression must have class type"
    }

}

Реализация CustomAllocator (должно быть в порядке):

template <typename T>
class CustomAllocator : public std::allocator<T>
{
private:
    using Base = std::allocator<T>;

public:
    T* allocate(size_t count){
        std::cout << ">> Allocating " << count << " elements" << std::endl;
        return Base::allocate(count);
    }

    T* allocate(size_t count, const void* p)
    {
        std::cout << ">> Allocating " << count << " elements" << std::endl;
        return Base::allocate(count, p);
    }

    void deallocate(T* p, size_t count)
    {
        if (p != nullptr)
        {
            std::cout << ">> Deallocating " << count << " elements" << std::endl;
            Base::deallocate(p, count);
        }
    }
};

Векторная реализация:

namespace MyLib
{
    template <typename T,
        template <typename Y> class Allocator = std::allocator>
    class Vector
    {
    private:
        std::size_t capacityV;
        std::size_t sizeV;
        Allocator<T> alloc;
        T* arr;

    public:
        typedef Allocator<T> AllocatorType;
        typedef Vector<T, Allocator> VectorType;
        using AllocTraits = std::allocator_traits<Allocator<T>>;

    public:
        explicit Vector(const AllocatorType& allocator = AllocatorType()) {
            capacityV = 0;
            sizeV = 0;
            alloc = allocator;  
            arr = nullptr;
        }
        Vector(const std::initializer_list<T>& values,
            const AllocatorType& allocator = AllocatorType()) {
            sizeV = values.size();
            alloc = allocator;

            if (sizeV < 128)
                capacityV = 128;
            else
                capacityV = (sizeV / 128) * 256;    //that makes sense

            arr = AllocTraits::allocate(alloc, capacityV);
            AllocTraits::construct(alloc, arr, capacityV);
            std::copy(values.begin(), values.end(), arr);
        }
        ~Vector() {
            if (arr)
                AllocTraits::deallocate(alloc, arr, capacityV);
        }

        Vector(const Vector& rhs) {
            capacityV = rhs.capacityV;
            sizeV = rhs.sizeV;
            arr = AllocTraits::allocate(alloc, capacityV);
            std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
        }
        Vector(Vector&& rhs) noexcept {
            capacityV = std::move(rhs.capacityV);
            sizeV = std::move(rhs.sizeV);
            arr = std::move(rhs.arr);
        }

        Vector& operator = (const Vector& rhs) {
            capacityV = rhs.capacityV;
            sizeV = rhs.sizeV;
            arr = AllocTraits::allocate(alloc, capacityV);
            std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
        }
        Vector& operator = (Vector&& rhs) {
            capacityV = std::move(rhs.capacityV);
            sizeV = std::move(rhs.sizeV);
            arr = std::move(rhs.arr);
        }

        T& operator [](std::size_t i) noexcept {
            if (i < sizeV)
                return arr[i];
            else
                throw std::out_of_range("Wrong index!");
        }
        const T& operator [](std::size_t i) const noexcept {
            if (i < sizeV)
                return arr[i];
            else
                throw std::out_of_range("Wrong index!");
        }

        T* data() noexcept {
            return arr;
        }
        const T* data() const noexcept {
            return arr;
        }

        void push_back(const T& value) {
            ++sizeV;
            if (!arr) {
                if (!capacityV)
                    capacityV = 128;
                arr = AllocTraits::allocate(alloc, capacityV);
            }
            if (sizeV > capacityV) {
                if(capacityV > UINT32_MAX - 256)
                    throw std::runtime_error("Vector overflowed!");
                size_t tmpCap = capacityV;
                capacityV = (sizeV / 128) * 256;    //Увеличим capacityV

                T* buf = AllocTraits::allocate(alloc, capacityV);
                std::move(arr, arr + sizeV - 1, buf);
                AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
                arr = buf;
            }
            arr[sizeV - 1] = value;
        }
        void push_back(T&& value) {
            ++sizeV;
            if (!arr) {
                if (!capacityV)
                    capacityV = 128;
                arr = AllocTraits::allocate(alloc, capacityV);
            }
            if (sizeV > capacityV) {
                if (capacityV > UINT32_MAX - 256)
                    throw std::runtime_error("Vector overflowed!");
                size_t tmpCap = capacityV;
                capacityV = (sizeV / 128) * 256;    //Увеличим capacityV

                T* buf = AllocTraits::allocate(alloc, capacityV);
                std::move(arr, arr + sizeV - 1, buf);
                AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
                arr = buf;
            }
            arr[sizeV - 1] = std::move(value);
        }

        void pop_back() {
            --sizeV;
        }

        void resize(std::size_t size) {
            if (this->sizeV == size)
                return;

            if (this->sizeV > size) {
                this->sizeV = size;
            }
            else {
                size_t tmpSize = size;
                if (capacityV >= size) {
                    this->sizeV = size;
                    for (size_t i = tmpSize - 1; i < this->sizeV; i++)
                        arr[i] = 0;
                }
                else {
                    size_t tmpCap = capacityV;
                    capacityV = (size / 128) * 256; //that makes sense

                    T* buf = AllocTraits::allocate(alloc, capacityV);
                    std::move(arr, arr + sizeV - 1, buf);
                    AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
                    arr = buf;

                    this->sizeV = size;
                    for (size_t i = tmpSize - 1; i < this->sizeV; i++)
                        arr[i] = 0;
                }
            }
        }
        void reserve(std::size_t capacity) {
            if (capacity > this->capacityV)
            {
                size_t tmpCap = capacity;
                this->capacityV = capacity;

                T* buf = AllocTraits::allocate(alloc, capacityV);
                std::move(arr, arr + sizeV - 1, buf);
                AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
                arr = buf;
            }
        }

        std::size_t size() const noexcept {
            return sizeV;
        }
        std::size_t capacity() const noexcept {
            return capacityV;
        }
        bool empty() const noexcept {
            return (sizeV == 0);
        }
    };
}

1 Ответ

4 голосов
/ 08 октября 2019
Vector<int> v(CustomAllocator<int>());

Вы получили удар по самому неприятному анализу . Vector<int> v(CustomAllocator<int>()); может быть проанализировано как объявление переменной или объявление функции, и грамматика предпочитает последнее. Поэтому компилятор считает, что v - это функция, и поэтому вы получаете ошибку «выражение должно иметь тип класса» - вы можете вызывать методы только для значений с типом класса, но v - это функция.

Даже если вы исправили эту ошибку, используя одну из следующих опций:

// C++03 solution (extra parens)
Vector<int> v((CustomAllocator<int>()));

// C++11 solution (uniform initialization)
Vector<int> v{CustomAllocator<int>{}};

Ваш код по-прежнему не будет выполнять то, что вы ожидали, , хотя он будет работать. Vector<int> это то же самое, что и Vector<int, std::allocator>, поэтому v будет по-прежнему использовать стандартный распределитель.

Почему это не вызывает ошибку компиляции? Поскольку CustomAllocator<int> наследует std::allocator<int> (чего не должно быть!), Поэтому конструктор копирования std::allocator<int> используется для нарезки вашего пользовательского распределителя в std::allocator<int>, а затем программа переходит с использованием стандартногораспределитель. Ваше CustomAllocator<int> временное преобразование в основном преобразуется в std::allocator<int>.

. Для иллюстрации оба приведенных выше "фиксированных" примера примерно эквивалентны этому коду (если мы игнорируем некоторые значения-копии/ перемещает, которые не имеют отношения к наблюдаемому поведению программы):

// Creates a custom allocator value.
CustomAllocator<int> a;

// Custom allocator is converted to std::allocator<int>.
std::allocator<int> b(a);

// std::allocator<int> is provided to the Vector constructor.
Vector<int> v(b);

Правильное исправление - указать второй параметр типа Vector, и тогда аргументы конструктора даже не нужны, так как конструктор по умолчанию будет делатьправильная вещь:

Vector<int, CustomAllocator> v;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...