处理自定义向量 类

Handling custom vector classes

我遇到过很多次我想要在向量中选择一个项目,为此我编写了模板 class:

// a vector wrapper which allows a specific item to be currently selected
template<typename T>
class  VectorSelectable
{
public:
    VectorSelectable() {};
    VectorSelectable(std::initializer_list<T> items) : m_Items(items) {};
    void Add(const T& v)                { m_Items.push_back(v); m_CurrentIndex = m_Items.size()-1; } // lvalue & refs
    void Add(T&& v)                     { m_Items.push_back(std::move(v)); m_CurrentIndex = m_Items.size()-1; } // rvalue
    void Remove(size_t index) { 
        assert(index < m_Items.size()); 
        m_Items.erase(m_Items.begin() + index);
        if(m_CurrentIndex != -1 && (int)index <= m_CurrentIndex) 
            m_CurrentIndex--; 
    }
    void RemoveCurrent()                { assert(m_CurrentIndex > -1 && m_CurrentIndex < (int)m_Items.size()); Remove(m_CurrentIndex); }
    T& CurrentItem()                    { assert(m_CurrentIndex > -1 && m_CurrentIndex < (int)m_Items.size()); return m_Items[m_CurrentIndex]; }
    T& operator [](size_t index)        { assert(index < Size()); return m_Items[index]; }
    // moves value of n_next onto n, and n_new onto n
    void ItemSwap(size_t n, size_t n_Next) {   
        assert(n < m_Items.size()); 
        assert(n_Next < m_Items.size()); 
        T itemBuf = std::move(m_Items[n]);                
        m_Items[n] = m_Items[n_Next];
        m_Items[n_Next] = std::move(itemBuf);
    }
    size_t Size()                       { return m_Items.size(); }
    const std::vector<T>& Data()        { return m_Items; }
    std::vector<T>* DataPtr()           { return &m_Items; }
    T* ItemPtr(size_t index)            { assert(index < m_Items.size()); return &m_Items[index]; }
    void SetCurrentIndex(int index)     { assert(index >= -1 && index < (int)m_Items.size()); m_CurrentIndex = index; }
    int& CurrentIndex()                 { return m_CurrentIndex; }
    bool HasItemSelected()              { return m_CurrentIndex != -1; }
private:
    std::vector<T> m_Items;
    int m_CurrentIndex = -1;
};

我也遇到过很多需要 unique_ptrs 向量的场景(通常用于多态 classes),看起来像这样:

template<typename T>
class Vector_UniquePtrs
{
public:
    // Adds an Item (and returns a raw ptr to it)
    // usage: v.Add() ... (equivelent to v.Add<base_class>())
    template<typename... Args>
    T* Add(Args... args) {
        return Add<T>(args...);
    }
    // Adds a Polymorphic Item (and returns a raw ptr to it)
    // usage: v.Add<sub_class>()
    template<typename T2, typename... Args>
    T* Add(Args... args) {
        m_Items.push_back(std::unique_ptr<T>(new T2(args...)));
        return m_Items.back().get();
    }
    // Remove Item
    void Remove(size_t index) { 
        assert(index < m_Items.size()); 
        m_Items.erase(m_Items.begin() + index);
    }
    T* operator [](size_t index)            { assert(index < Size()); return m_Items[index].get(); }
    size_t Size()                       { return m_Items.size(); }
private:
    std::vector<std::unique_ptr<T>> m_Items;
};

我的问题是:

你主要需要把std::vector<std::unique_ptr<T>>放在VectorSelectable里,然后从界面上隐藏所有指针的东西。对您的 class 进行一些小改动后,它可能看起来像这样:

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>

template <typename T>
class VectorPtrSelectable {
public:
    VectorPtrSelectable() = default;

    VectorPtrSelectable(std::initializer_list<T> items) :
        m_CurrentIndex(items.size() - 1) 
    {
        m_Items.reserve(items.size());
        // fill `m_Items` from the initializer list ...
        std::transform(items.begin(), items.end(), std::back_inserter(m_Items),
            [](const T& item) {
                // ... by creating a unique_ptr from each element (transformation)
                return std::make_unique<T>(item);
            });
    };

    template <class U, class... Args>
    T& Add(Args&&... args) {
        // make `Add` forward to `make_unique`
        m_Items.emplace_back(std::make_unique<U>(std::forward<Args>(args)...));
        m_CurrentIndex = m_Items.size() - 1;
        // and return a reference instead
        return *m_Items.back();
    }

    template <class... Args>
    T& Add(Args&&... args) {
        // forward to Add<U>
        return Add<T>(std::forward<Args>(args)...);
    }

    void Remove(size_t index) {
        m_Items.erase(std::next(m_Items.begin(), index));
        if (m_CurrentIndex != static_cast<size_t>(-1) && index <= m_CurrentIndex)
            m_CurrentIndex--;
    }

    T& operator[](size_t index) { return *m_Items[index]; }
    const T& operator[](size_t index) const { return *m_Items[index]; }

    T& CurrentItem() { return *m_Items[m_CurrentIndex]; }
    const T& CurrentItem() const { return *m_Items[m_CurrentIndex]; }

    void SetCurrentIndex(size_t index) { m_CurrentIndex = index; }
    void RemoveCurrent() { Remove(m_CurrentIndex); }
    bool HasItemSelected() { return m_CurrentIndex != static_cast<size_t>(-1); }

    void ItemSwap(size_t n, size_t n_Next) {
        // simplified swapping:
        std::swap(m_Items[n], m_Items[n_Next]);
    }
    
    // make functions that does not change your instance const qualified:
    size_t CurrentIndex() const { return m_CurrentIndex; }
    size_t Size() const { return m_Items.size(); }

private:
    std::vector<std::unique_ptr<T>> m_Items;
    size_t m_CurrentIndex = static_cast<size_t>(-1);  // size_t for the index
};

用法示例:

#include <iostream>
#include <string>

int main() {
    VectorPtrSelectable<std::string> vs{"World", "Hello"};
    std::cout << vs.CurrentItem() << '\n';
    vs.ItemSwap(0, 1);
    std::cout << vs.CurrentItem() << '\n';
    vs.RemoveCurrent();
    std::cout << vs.CurrentItem() << '\n';
    std::cout << vs.Add("Add and get a reference") << '\n';
}

输出:

Hello
World
Hello
Add and get a reference
  • 我将 m_CurrentIndex 设为 size_t,因为这是惯用的,但如果您想将其保留为 int,那也很好。
  • std::next(m_Items.begin(), index) 将与 m_Items.begin() + index 执行相同的操作,但在由 m_Items.begin() 编辑的迭代器 return 是普通指针的情况下,使用 std::next避免有关使用指针算法的潜在警告。
  • 返回引用而不是指向所添加元素的指针除了使界面更加惯用之外没有任何区别。这正是 class 的用户可能期望的。返回一个指针也会引发诸如 "can it return nullptr?" 等问题
  • 添加的 const 限定函数使这些函数也可以在 const 上下文中使用。
    template<class T>
    void foo(const VectorPtrSelectable<T>& vps) { // note: const&
        if(vps.Size() > 0) {
            std::cout << "the first element is " << vps[0] << '\n';
            std::cout << "the current element is " << vps.CurrentItem() << '\n';
        }
    }
    
    上面使用的三个成员函数中的 None 可以在没有 const 限定重载的情况下使用。