暴露std::vector接口的更好方法是什么?

3
我正在编写一个std::vector包装器,它使用自定义分配器通过.dll与PascalScript解释器通信。下面的代码可以正常工作,但更新和编写都很繁琐,而且会让我感到眼花缭乱。
在这个解释器中,数组是连续存储的,其大小存储在&Array_Ptr [0] - sizeof(int)处。
想要使用std::vector来处理这些数组,我决定编写一个自定义分配器,它分配size + sizeof(int)并将向量包装器放置在ptr [0]处。因此,数据存储在&ptr [0] + sizeof(int)处。
问题是如果我想使用向量接口,我必须手动编写所有std::vector函数,因为从中继承将是不好的?
我想出了以下代码:
template<typename T>
class PascalAllocator : public BasicAllocator<T> //BasicAllocator is equivalent to std::allocator with minor changes.
{
    public:
        typedef typename BasicAllocator<T>::pointer pointer;
        typedef typename BasicAllocator<T>::size_type size_type;
        typedef typename BasicAllocator<T>::value_type value_type;

        template<typename U>
        struct rebind {typedef PascalAllocator<U> other;};

        pointer allocate(size_type n, const void* hint = 0)
        {
            std::int32_t* data_ptr = reinterpret_cast<std::int32_t*>(::operator new((n * sizeof(value_type)) + sizeof(std::int32_t)));
            return reinterpret_cast<pointer>(++data_ptr);
        }

        void deallocate(void* ptr, size_type n)
        {
            if (ptr)
            {
                std::int32_t* data_ptr = reinterpret_cast<std::int32_t*>(ptr);
                ::operator delete(reinterpret_cast<T*>(--data_ptr));
            }
        }
};

template<typename T, typename Allocator = PascalAllocator<T>>
class PascalVector
{
    private:
        std::vector<T, Allocator> Data;
        inline std::int32_t* size_ptr() {return reinterpret_cast<std::int32_t*>(&Data[0]) - 1;}
        inline const std::int32_t* size_ptr() const {return reinterpret_cast<std::int32_t*>(&Data[0]) - 1;}

    public:
        typedef std::size_t                                          size_type;
        typedef std::ptrdiff_t                                       difference_type;
        typedef T*                                                   pointer;
        typedef const T*                                             const_pointer;
        typedef T&                                                   reference;
        typedef const T&                                             const_reference;
        typedef T                                                    value_type;
        typedef typename std::vector<T, Allocator>::iterator         iterator;
        typedef typename std::vector<T, Allocator>::const_iterator   const_iterator;
        typedef std::reverse_iterator<const_iterator>                const_reverse_iterator;
        typedef std::reverse_iterator<iterator>                      reverse_iterator;

        explicit PascalVector(const Allocator& alloc = Allocator()) : Data(std::forward<decltype(alloc)>(alloc)) {*size_ptr() = 0;}
        explicit PascalVector(size_type size, const Allocator& alloc = Allocator()) : Data(size, std::forward<decltype(alloc)>(alloc)) {*size_ptr() = size - 1;}
        explicit PascalVector(size_type size, const T &value, const Allocator& alloc = Allocator()) : Data(size, std::forward<decltype(value)>(value), std::forward<decltype(alloc)>(alloc)) {*size_ptr() = size - 1;}

        template<class InputIt>
        PascalVector(InputIt first, InputIt second, const Allocator &alloc = Allocator()) : Data(first, second, std::forward<decltype(alloc)>(alloc)) {*size_ptr() = Data.size() - 1;}

        PascalVector(const PascalVector &other) : Data(other.Data) {}
        PascalVector(const PascalVector &other, const Allocator& alloc) : Data(other.Data, std::forward<decltype(alloc)>(alloc)) {}
        PascalVector(PascalVector && other) : Data(std::move(other.Data)) {}
        PascalVector(PascalVector && other, const Allocator& alloc) : Data(std::move(other.Data), std::move(alloc)) {}
        PascalVector(const std::initializer_list<T> &init, const Allocator& alloc = Allocator()) : Data(init, alloc) {}

        inline PascalVector& operator = (PascalVector other) {Data.operator = (std::forward<decltype(other.Data)>(other.Data)); return *this;}
        inline PascalVector& operator = (std::initializer_list<T> ilist) {Data.operator = (std::forward<decltype(ilist)>(ilist)); return *this;}
        inline PascalVector& operator = (PascalVector && other) {Data.operator = (std::forward<decltype(other.Data)>(other.Data)); return *this;}

        template<class InputIt>
        inline void assign(InputIt first, InputIt second) {Data.assign(first, second);};
        inline void assign(size_type count, const T& value) {Data.assign(count, std::forward<decltype(value)>(value));}
        inline void assign(std::initializer_list<T> ilist) {Data.assign(std::forward<decltype(ilist)>(ilist));}
        inline Allocator get_allocator() const {return Data.get_allocator();}

        inline reference at(size_type pos) {return Data.at(pos);}
        inline const_reference at(size_type pos) const {return Data.at(pos);}
        inline reference operator[](size_type pos) {return Data[pos];}
        inline const_reference operator[](size_type pos) const {return Data[pos];}
        inline reference front() {return Data.front();}
        inline constexpr const_reference front() const {return Data.front();}
        inline reference back() {return Data.back();}
        inline constexpr const_reference back() const {return Data.back();}
        inline pointer data() {return Data.data();}
        inline const_pointer data() const {return Data.data();}

        inline iterator begin() {return Data.begin();}
        inline const_iterator begin() const {return Data.begin();}
        inline const_iterator cbegin() const {return Data.cbegin();}
        inline iterator end() {return Data.end();}
        inline const_iterator end() const {return Data.end();}
        inline const_iterator cend() const {return Data.cend();}
        inline reverse_iterator rbegin() {return Data.rbegin();}
        inline const_reverse_iterator rbegin() const {return Data.rbegin();}
        inline const_reverse_iterator crbegin() const {return Data.rbegin();}
        inline reverse_iterator rend() {return Data.rend();}
        inline const_reverse_iterator rend() const {return Data.rend();}
        inline const_reverse_iterator crend() const {return Data.crend();}

        inline bool empty() const {return Data.empty();}
        inline size_type size() const {return Data.size();}
        inline size_type max_size() const {return Data.max_size();}
        inline void reserve(size_type new_cap) {Data.reserve(size);}
        inline size_type capacity() const {return Data.capacity();}
        inline void shrink_to_fit() {Data.shrink_to_fit(); *size_ptr() = Data.size() - 1;}

        inline void clear() {Data.clear(); *size_ptr() = 0;}

        inline iterator insert(iterator pos, const T& value) {return Data.insert(pos, std::forward<decltype(value)>(value));  *size_ptr() = Data.size() - 1;}
        inline void insert(iterator pos, size_type count, const T& value) {Data.insert(pos, count, std::forward<decltype(value)>(value));  *size_ptr() = Data.size() - 1;}
        template<class InputIt>
        inline void insert(iterator pos, InputIt first, InputIt last) {Data.insert(pos, first, last);  *size_ptr() = Data.size() - 1;}
        inline void insert(iterator pos, std::initializer_list<T> ilist) {Data.insert(pos, std::forward<decltype(ilist)>(ilist));  *size_ptr() = Data.size() - 1;}

        template<class... Args>
        inline iterator emplace(iterator pos, Args && ... args) {iterator res = Data.emplace(pos, std::forward<Args>(args)...);  *size_ptr() = Data.size() - 1; return res;}
        template<class... Args>
        inline void emplace_back(Args && ... args) {Data.emplace_back(std::forward<Args>(args)...);  *size_ptr() = Data.size() - 1;}

        inline iterator erase(iterator pos) {iterator res = Data.erase(pos); *size_ptr() = Data.size(); return res;}
        inline iterator erase(iterator first, iterator last) {iterator res = Data.erase(first, last);  *size_ptr() = Data.size() - 1; return res;}

        inline void push_back(const T& value) {Data.push_back(std::forward<decltype(value)>(value)); *size_ptr() = Data.size() - 1;}
        inline void push_back(T && value) {Data.push_back(std::forward<T>(value)); *size_ptr() = Data.size() - 1;}
        inline void pop_back() {Data.pop_back(); *size_ptr() = Data.size() - 1;}

        inline void resize(size_type count, T value = T()) {Data.resize(count, std::forward<decltype(value)>(value)); *size_ptr() = count - 1;}
        inline void swap(PascalVector& other) {Data.swap(other.Data);}
};

现在的 PascalAllocator 很好。让我感到困扰的是 PascalVector 接口。
有什么更简单的方法吗?

编辑: 根据我收到的一些回复,我尝试按以下方式实现自己的向量:
template<typename T, typename Allocator = PascalAllocator<T>>
class PSArray : private Allocator
{
    private:
        typename Allocator::pointer first;
        typename Allocator::pointer last;
        typename Allocator::size_type _size;
        typename Allocator::pointer allocmem(typename Allocator::size_type n, const T& value);
        void deallocmem();

    public:
        typedef T                                                    value_type;
        typedef typename Allocator::pointer                          pointer;
        typedef typename Allocator::const_pointer                    const_pointer;
        typedef typename Allocator::reference                        reference;
        typedef typename Allocator::const_reference                  const_reference;
        typedef typename Allocator::size_type                        size_type;
        typedef typename Allocator::difference_type                  difference_type;
        typedef typename Allocator::pointer                          iterator;
        typedef typename Allocator::const_pointer                    const_iterator;
        typedef std::reverse_iterator<const_iterator>                const_reverse_iterator;
        typedef std::reverse_iterator<iterator>                      reverse_iterator;
        typedef Allocator                                            allocator_type;

        allocator_type get_allocator() const {return static_cast<const Allocator&>(*this);}

        iterator begin()             {return first;}
        iterator end()               {return last;}
        const_iterator begin() const {return first;}
        const_iterator end() const   {return last;}
        size_type size() const {return _size;}

        PSArray(size_type n = 0, const T& value = T(), const Allocator& alloc = Allocator());
        PSArray(const PSArray &other);
        PSArray(PSArray&& other);
        ~PSArray();

        PSArray& operator = (const PSArray &other);
        inline reference operator[](size_type pos) {return first[pos];}
        inline const_reference operator[](size_type pos) const {return first[pos];}
};

template <class T, class Allocator>
PSArray<T, Allocator>::PSArray(size_type n, const T& value, const Allocator& alloc) : Allocator(alloc), first(0), last(0), _size(0)
{
    this->first = this->allocmem(n, value);
    this->last = &first[0] + n + 1;
    reinterpret_cast<std::int32_t*>(first)[-1] = n - 1; //*(reinterpret_cast<std::int32_t*>(&first[0]) - 1) = n - 1;
    _size = n;
}

template <class T, class Allocator>
PSArray<T, Allocator>::PSArray(const PSArray &other) : Allocator(other.get_allocator()), first(0), last(0), _size(other._size)
{
    this->first = Allocator::allocate(other._size);
    this->last = &first[0] + _size + 1;
    memcpy(&first[0], &other.first[0], other._size * sizeof(T));
    reinterpret_cast<std::int32_t*>(first)[-1] = _size - 1; //*(reinterpret_cast<std::int32_t*>(&first[0]) - 1) = _size - 1;
}

template <class T, class Allocator>
PSArray<T, Allocator>::PSArray(PSArray&& other) : first(other.first), last(other.last), _size(other._size)
{
    other.first = nullptr;
    other.last = nullptr;
    other._size = 0;
}

template <class T, class Allocator>
PSArray<T, Allocator>::~PSArray()
{
    this->deallocmem();
}

template <class T, class Allocator>
PSArray<T, Allocator>& PSArray<T, Allocator>::operator = (const PSArray &other)
{
    _size = other._size;
    this->first = Allocator::allocate(other._size);
    this->last = &first[0] + _size + 1;
    memcpy(&first[0], &other.first[0], other._size * sizeof(T));
    reinterpret_cast<std::int32_t*>(first)[-1] = _size - 1;
    return *this;
}

template <class T, class Allocator>
typename Allocator::pointer PSArray<T, Allocator>::allocmem(typename Allocator::size_type n, const T& value)
{
    if (n != 0)
    {
        size_type i = 0;
        typename Allocator::pointer res = Allocator::allocate(n);

        try
        {
            for (i = 0; i < n; ++i)
            {
                Allocator::construct(res + i, value);
            }
        }
        catch(...)
        {
            for(size_type j = 0; j < i; ++j)
            {
                Allocator::destroy(res + j);
            }
            Allocator::deallocate(res, n);
            throw;
        }
        return res;
    }
    return nullptr;
}

template <class T, class Allocator>
void PSArray<T, Allocator>::deallocmem()
{
    if (first != last)
    {
        for (iterator i = first; i < last; ++i)
        {
            Allocator::destroy(i);
        }
        Allocator::deallocate(first, last - first);
    }
}

到目前为止,它的表现相当不错。但与之前的代码相比,需要更多的工作。


8
为什么不直接使用自定义分配器创建一个标准的 std::vector 呢?例如: template<typename T> using PascalVector = std::vector<T, PascalAllocator<T>>; - Some programmer dude
1
你可以使用从vector<T, Allocator>私有继承来减少样板代码,而不是使用数据成员。你仍然需要一堆using语句,每个不同的名称都需要一个,但比所有那些传递函数要少。 - Steve Jessop
1
@CantChooseUsernames:从vector公开继承有点糟糕,但私有继承就没问题了。从未设计用于公开继承的类进行公开继承有两个问题:(1)vector没有虚析构函数,因此用户需要知道不要这样做:vector<int, PascalAllocator<int>> *ptr = new PascalVector<int>(); delete ptr;。实际上,这并不是很难安排,只是意味着你的类有一个小小的陷阱。(2)没有精确定义vector的哪些函数会相互调用,而且在派生类中重载的任何内容都不能被基类调用。 - Steve Jessop
1
问题(2)也影响了私有继承,这意味着无论何时你尝试使用using通过函数传递,你都需要考虑基类中的实现是否足够。您可能错误地假定,例如,clear调用erase(begin(), end())并且您已经重载了erase,所以没问题。但那行不通(即使clear调用了erase,它也无法调用您重载的erase)。您还需要重载clear,而事实上您已经这样做了。所以你必须知道自己在做什么 :-) - Steve Jessop
1
使用reinterpret_castvoid*转换并不必要。使用static_cast就足够了(而且可以更好地表达意图)。 - dyp
显示剩余7条评论
1个回答

5
没有比实现整个std::vector接口更容易将PascalVector变成std::vector的方式了。
  • 不要公开继承std::vector,因为std::vector没有虚析构函数。这意味着,即使指针指向PascalVector,删除指向std::vector的指针也不会调用PascalVector的析构函数。
  • 不要私有继承std::vector,因为由于std::vector中缺少虚函数,你将会针对实现而非接口进行编程。
  • 使用PascalAllocator定义std::vector的typedef是不够的,因为PascalAllocator无法获知向量大小的变化(因此无法将大小写入正确位置)。

另外,类内定义的函数会自动被视为inline。在这些情况下,inline关键字是多余的。

谢谢您提供的信息和回复。我想我可能会继续使用我现有的接口。如果您想看看我正在进行的跟踪,请查看我的Vector实现。也许只需放弃那段代码,坚持以前的方式,虽然有点繁琐。 - Brandon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接