如何创建类

时间:2017-08-17 19:13:13

标签: c++ c++11 vector variadic

我正在尝试创建充当多维向量的类。它没有任何花哨的东西。我基本上想要一个“容器”类foo,我可以通过foo [x] [y] [z]访问元素。现在我还需要类似foo [x] [y]和foo [x]的类。这引导我思考以下(更一般的)问题,有没有办法制作这样的东西,你可以初始化为任何n个参数的foo A(a,b,c,...)并得到一个n维向量,其元素可由[] [] [] ...访问?在我的课程下面(例如)四维案例。

首先是标题

   #ifndef FCONTAINER_H
   #define FCONTAINER_H
   #include <iostream>
   using namespace std;

   class Fcontainer
   {
   private:
           unsigned dim1, dim2, dim3, dim4 ;
           double* data;
   public:
           Fcontainer(unsigned const dims1, unsigned const dims2, unsigned const dims3, unsigned const dims4);
           ~Fcontainer();

           Fcontainer(const Fcontainer& m);
           Fcontainer& operator= (const Fcontainer& m);

           double& operator() (unsigned const dim1, unsigned const dim2, unsigned const dim3, unsigned const dim4);
           double const& operator() (unsigned const dim1, unsigned const dim2, unsigned const dim3, unsigned const dim4) const;
    };
    #endif // FCONTAINER_H

现在是cpp:

  #include "fcontainer.hpp"

  Fcontainer::Fcontainer(unsigned const dims1, unsigned const dims2, unsigned const dims3, unsigned const dims4)
  {
       dim1 = dims1; dim2 = dims2; dim3 = dims3; dim4 = dims4;
       if (dims1 == 0 || dims2 == 0 || dims3 == 0 || dims4 == 0)
          throw std::invalid_argument("Container constructor has 0 size");
       data = new double[dims1 * dims2 * dims3 * dims4];
  }

  Fcontainer::~Fcontainer()
  {
      delete[] data;
  }

  double& Fcontainer::operator() (unsigned const dims1, unsigned const dims2, unsigned const dims3, unsigned const dims4)
  {
       if (dims1 >= dim1 || dims2 >= dim2 || dims3 >= dim3 || dims4 >= dim4)
           throw std::invalid_argument("Container subscript out of bounds");
       return data[dims1*dim2*dims3*dim4 + dims2*dim3*dim4 + dim3*dim4 + dims4];
  }

  double const& Fcontainer::operator() (unsigned const dims1, unsigned const dims2, unsigned const dims3, unsigned const dims4) const
  {
     if(dims1 >= dim1 || dims2 >= dim2 || dims3 >= dim3 || dims4 >= dim4)
         throw std::invalid_argument("Container subscript out of bounds");
      return data[dims1*dim2*dims3*dim4 + dims2*dim3*dim4 + dim3*dim4 + dims4];
  }

所以我想把它扩展到任意数量的维度。我想它会采用可变参数模板或std :: initializer_list的方法,但我不清楚如何处理这个问题(对于这个问题)。

2 个答案:

答案 0 :(得分:2)

在Visual Studio中闲逛了一会儿,我想出了这个废话:

template<typename T>
class Matrix {
    std::vector<size_t> dimensions;
    std::unique_ptr<T[]> _data;

    template<typename ... Dimensions>
    size_t apply_dimensions(size_t dim, Dimensions&& ... dims) {
        dimensions.emplace_back(dim);
        return dim * apply_dimensions(std::forward<Dimensions>(dims)...);
    }

    size_t apply_dimensions(size_t dim) {
        dimensions.emplace_back(dim);
        return dim;
    }
public:
    Matrix(std::vector<size_t> dims) : dimensions(std::move(dims)) {
        size_t size = flat_size();
        _data = std::make_unique<T[]>(size);
    }

    template<typename ... Dimensions>
    Matrix(size_t dim, Dimensions&&... dims) {
        size_t size = apply_dimensions(dim, std::forward<Dimensions>(dims)...);
        _data = std::make_unique<T[]>(size);
    }

    T & operator()(std::vector<size_t> const& indexes) {
        if(indexes.size() != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        return _data[get_flat_index(indexes)];
    }

    T const& operator()(std::vector<size_t> const& indexes) const {
        if (indexes.size() != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        return _data[get_flat_index(indexes)];
    }

    template<typename ... Indexes>
    T & operator()(size_t idx, Indexes&& ... indexes) {
        if (sizeof...(indexes)+1 != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        size_t flat_index = get_flat_index(0, idx, std::forward<Indexes>(indexes)...);
        return at(flat_index);
    }

    template<typename ... Indexes>
    T const& operator()(size_t idx, Indexes&& ... indexes) const {
        if (sizeof...(indexes)+1 != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        size_t flat_index = get_flat_index(0, idx, std::forward<Indexes>(indexes)...);
        return at(flat_index);
    }

    T & at(size_t flat_index) {
        return _data[flat_index];
    }

    T const& at(size_t flat_index) const {
        return _data[flat_index];
    }

    size_t dimension_size(size_t dim) const {
        return dimensions[dim];
    }

    size_t num_of_dimensions() const {
        return dimensions.size();
    }

    size_t flat_size() const {
        size_t size = 1;
        for (size_t dim : dimensions)
            size *= dim;
        return size;
    }

private:
    size_t get_flat_index(std::vector<size_t> const& indexes) const {
        size_t dim = 0;
        size_t flat_index = 0;
        for (size_t index : indexes) {
            flat_index += get_offset(index, dim++);
        }
        return flat_index;
    }

    template<typename ... Indexes>
    size_t get_flat_index(size_t dim, size_t index, Indexes&& ... indexes) const {
        return get_offset(index, dim) + get_flat_index(dim + 1, std::forward<Indexes>(indexes)...);
    }

    size_t get_flat_index(size_t dim, size_t index) const {
        return get_offset(index, dim);
    }

    size_t get_offset(size_t index, size_t dim) const {
        if (index >= dimensions[dim])
            throw std::runtime_error("Index out of Bounds");
        for (size_t i = dim + 1; i < dimensions.size(); i++) {
            index *= dimensions[i];
        }
        return index;
    }
};

让我们来谈谈这段代码的用处。

//private:
    template<typename ... Dimensions>
    size_t apply_dimensions(size_t dim, Dimensions&& ... dims) {
        dimensions.emplace_back(dim);
        return dim * apply_dimensions(std::forward<Dimensions>(dims)...);
    }

    size_t apply_dimensions(size_t dim) {
        dimensions.emplace_back(dim);
        return dim;
    }
public:
    Matrix(std::vector<size_t> dims) : dimensions(std::move(dims)) {
        size_t size = flat_size();
        _data = std::make_unique<T[]>(size);
    }

    template<typename ... Dimensions>
    Matrix(size_t dim, Dimensions&&... dims) {
        size_t size = apply_dimensions(dim, std::forward<Dimensions>(dims)...);
        _data = std::make_unique<T[]>(size);
    }

此代码使我们能够做的是为此矩阵编写一个初始化程序,该矩阵采用任意数量的维度。

int main() {
    Matrix<int> mat{2, 2}; //Yields a 2x2 2D Rectangular Matrix
    mat = Matrix<int>{4, 6, 5};//mat is now a 4x6x5 3D Rectangular Matrix
    mat = Matrix<int>{9};//mat is now a 9-length 1D array.
    mat = Matrix<int>{2, 3, 4, 5, 6, 7, 8, 9};//Why would you do this? (yet it compiles...)
}

如果维度的数量和大小仅在运行时已知,则此代码将解决此问题:

int main() {
    std::cout << "Input the sizes of each of the dimensions.\n";
    std::string line;
    std::getline(std::cin, line);
    std::stringstream ss(line);
    size_t dim;
    std::vector<size_t> dimensions;
    while(ss >> dim)
        dimensions.emplace_back(dim);

    Matrix<int> mat{dimensions};//Voila.
}

然后,我们希望能够访问该矩阵的任意索引。此代码提供了两种方法:静态使用模板,或在运行时可变。

//public:
    T & operator()(std::vector<size_t> const& indexes) {
        if(indexes.size() != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        return _data[get_flat_index(indexes)];
    }

    T const& operator()(std::vector<size_t> const& indexes) const {
        if (indexes.size() != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        return _data[get_flat_index(indexes)];
    }

    template<typename ... Indexes>
    T & operator()(size_t idx, Indexes&& ... indexes) {
        if (sizeof...(indexes)+1 != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        size_t flat_index = get_flat_index(0, idx, std::forward<Indexes>(indexes)...);
        return at(flat_index);
    }

    template<typename ... Indexes>
    T const& operator()(size_t idx, Indexes&& ... indexes) const {
        if (sizeof...(indexes)+1 != dimensions.size())
            throw std::runtime_error("Incorrect number of parameters used to retrieve Matrix Data!");
        size_t flat_index = get_flat_index(0, idx, std::forward<Indexes>(indexes)...);
        return at(flat_index);
    }

然后,在实践中:

Matrix<int> mat{6, 5};
mat(5, 2) = 17;
//mat(5, 1, 7) = 24; //throws exception at runtime because of wrong number of dimensions.
mat = Matrix<int>{9, 2, 8};
mat(5, 1, 7) = 24;
//mat(5, 2) = 17; //throws exception at runtime because of wrong number of dimensions.

这适用于运行时动态索引:

std::vector<size_t> indexes;
/*...*/
mat(indexes) = 54; //Will throw if index count is wrong, will succeed otherwise

此类对象可能需要许多其他功能,例如resize方法,但选择如何实现这是一个高级设计决策。我还遗漏了大量其他可能有价值的实现细节(比如一个优化的移动构造函数,一个比较运算符,一个复制构造函数),但是这应该会让你对如何启动有很好的了解。

编辑:

如果您想完全避免使用模板,可以减少此处提供的一半代码,只需使用使用std::vector<size_t>的方法/构造函数来提供维度/索引数据。如果您不需要能够在运行时动态调整维度数量,则可以删除std::vector<size_t>重载,甚至可能使维度数量成为类本身的模板参数(这将使您能够使用size_t[]std::array[size_t, N]存储维度数据。)

答案 1 :(得分:0)

嗯,假设您完全关心效率,您可能希望以连续的方式存储所有元素,无论如何。所以你可能想做类似的事情:

template <std::size_t N, class T>
class MultiArray {
    MultiArray(const std::array<std::size_t, N> sizes)
      : m_sizes(sizes)
      , m_data.resize(product(m_sizes)) {}

    std::array<std::size_t, N> m_sizes;
    std::vector<T> m_data;
};

索引部分是一种有趣的地方。基本上,如果您希望a[1][2][3]等工作,您必须让a返回某种代理对象,它有自己的operator[]。每个人都必须意识到自己的等级。每次执行[]时,它都会返回一个代理,让您指定下一个索引。

template <std::size_t N, class T>
class MultiArray {
   // as before

   template <std::size_t rank>
   class Indexor {
       Indexor(MultiArray& parent, const std::array<std::size_t, N>& indices = {})
         : m_parent(parent), m_indices(indices) {}

       auto operator[](std::size_t index) {
           m_indices[rank] = index;
           return Indexor<rank+1>(m_indices, m_parent);
       }
       std::array<std::size_t, N> m_indices;
       MultiArray& m_parent;
   };

   auto operator[](std::size_t index) {
       return Indexor<0>(*this)[index];
   }
}

最后,您对最后一个索引的完成情况有专长:

   template <>
   class Indexor<N-1> { // with obvious constructor
       auto operator[](std::size_t index) {
           m_indices[N-1] = index;
           return m_parent.m_data[indexed_product(m_indices, m_parent.m_sizes)];
       }
       std::array<std::size_t, N> m_indices;
       MultiArray& m_parent;
   };

显然这是一个草图,但此时它只是填写细节并将其编译。还有其他一些方法,比如让indexor对象有两个迭代器和缩小,但这看起来有点复杂。你也不需要模板化Indexor类,而是可以使用运行时整数,但是这会很容易被误用,有太多或太少的[]会是运行时错误,而不是编译时间。

编辑:您也可以按照17中描述的方式对其进行初始化,但不能在14中进行初始化。但是在14中你可以使用函数:

template <class ... Ts>
auto make_double_array(Ts ts) {
    return MultiArray<sizeof ... Ts, double>(ts...);
}

Edit2:我在实现中使用productindexed_product。第一个是显而易见的,第二个是不太明显的,但希望它们应该清楚。后者是一个赋予维数数组的函数,索引数组将返回该元素在数组中的位置。