In general, nesting std::vector is a great idea. It's usually best to plan on allocating memory that will contain your entire multidimensional matrix as a continuous block, and then index it as if it were multidimensional. This block of memory can be allocated via new , but if you do not need some precise control over how it was allocated (user allocator), I would recommend sticking to one std::vector .
It is not difficult to create a class for managing such a resource in which the number of dimensions can be set dynamically. A good way to organize such a class is to keep track of the allocated memory, the size of each dimension, and the step pattern for each dimension. The steps describe how many elements you need to increase in order to reach the next element in a given dimension.
This allows you to effectively index (just pointer arithmetic), as well as a very efficient rebuild: if the number of elements does not change, it just requires changing the shape array and step.
Example:
Here is a very simple class that will store such a dynamic multidimensional double s array. It stores data in a row order, which means that the last index is the fastest. Therefore, for a 2D array, the first row is stored contiguously, then the second row, etc.
You can resize the array by changing the number of dimensions if you wish. Access to the base element operator[] also shown. There is nothing unusual in this class, but you can extend it to provide any functionality that you need, such as iterators, mathematical operations with data, I / O operators, etc.
#ifndef _DYNAMIC_ARRAY_H_ #define _DYNAMIC_ARRAY_H_ #include <vector> #include <numeric> #include <functional> class dynamic_array { public: dynamic_array(const std::vector<int>& shape) : m_nelem(std::accumulate(shape.begin(), shape.end(), 1, std::multiplies<int>())) , m_ndim(shape.size()) , m_shape(shape) { compute_strides(); m_data.resize(m_nelem, 0.0); } ~dynamic_array() { } const double& operator[](int i) const { return m_data.at(i); } double& operator[](int i) { return m_data.at(i); } const double& operator[](const std::vector<int>& indices) const { auto flat_index = std::inner_product( indices.begin(), indices.end(), m_strides.begin(), 0); return m_data.at(flat_index); } double& operator[](const std::vector<int>& indices) { auto flat_index = std::inner_product( indices.begin(), indices.end(), m_strides.begin(), 0); return m_data.at(flat_index); } void reshape(const std::vector<int>& new_shape) { auto new_nelem = std::accumulate( new_shape.begin(), new_shape.end(), 1, std::multiplies<int>()); if (new_nelem != m_nelem) { throw std::invalid_argument("dynamic_array::reshape(): " "number of elements must not change."); } m_nelem = new_nelem; m_ndim = new_shape.size(); m_shape = new_shape; compute_strides(); } const std::vector<int>& shape() const { return m_shape; } const std::vector<int>& strides() const { return m_strides; } int ndim() const { return m_ndim; } int nelem() const { return m_nelem; } private: int m_ndim; int m_nelem; std::vector<int> m_shape; std::vector<int> m_strides; std::vector<double> m_data; void compute_strides() { m_strides.resize(m_ndim); m_strides.at(m_ndim - 1) = 1; std::partial_sum(m_shape.rbegin(), m_shape.rend() - 1, m_strides.rbegin() + 1, std::multiplies<int>()); } }; #endif // include guard
Here is a basic demonstration of functionality.
#include "dynamic_array.h" #include <iostream> int main(int /* argc */, const char * /* argv */[]) { dynamic_array arr({2, 3}); std::cout << "Shape: { "; for (auto& each : arr.shape()) std::cout << each << " "; std::cout << "}" << std::endl; std::cout << "Strides: { "; for (auto& each : arr.strides()) std::cout << each << " "; std::cout << "}" << std::endl; // Reshape array, changing number of dimensions, but // keeping number of elements constant. arr.reshape({6}); std::cout << "Shape: { "; for (auto& each : arr.shape()) std::cout << each << " "; std::cout << "}" << std::endl; // Verify that the stride pattern has now also changed. std::cout << "Strides: { "; for (auto& each : arr.strides()) std::cout << each << " "; std::cout << "}" << std::endl; return 0; }
You can compile a test program with g++ -std=c++14 -o test test.cc , assuming that the file defining the class is in the same directory as test.cc