1

I want to build a 3D nx*ny*nz matrix that nx,ny,nz are about 200 so I have to use dynamic allocation and because I have a lot of these matrixes I need build these matrixes in a way that it uses minimum memory as is possible how can I do this and how can I build in contiguous form?

peaceman
  • 1,499
  • 2
  • 17
  • 29
  • 3
    You might consider [Boost.MultiArray](http://www.boost.org/doc/libs/1_49_0/libs/multi_array/doc/index.html). – Oliver Charlesworth Mar 18 '12 at 16:30
  • 1
    If the memory is contiguous the space is trivially 200*200*200*sizeof(T) where T is the value type. The only way to reduce is usign small T (like short or even char, if they can fit the values you have to represent). Other techniques are about avoiding to store repeating values (like zeroes in sparse matrixes), but that are not contiguous. Your requirements are in contradiction – Emilio Garavaglia Mar 18 '12 at 16:40

3 Answers3

2

Using C++11:

template<typename T,size_t M,size_t N,size_t O>
using Matrix3D = std::array<std::array<std::array<T,O>,N>,M>;

std::unique_ptr<Matrix3D<double,200,200,200>> mat(new Matrix3D<double,200,200,200>);

(*mat)[m][n][o] = 10.0;

If you write a make_unique function the variable declaration becomes:

auto mat = std::make_unique<Matrix3D<double,200,200,200>>();

So a whole program might look like:

#include <memory> // std::unique_ptr for convenient and exception safe dynamic-memory management
#include <array>  // std::array because it behaves much better than raw arrays 

template<typename T,size_t M,size_t N,size_t O>
using Matrix3D = std::array<std::array<std::array<T,O>,N>,M>;

// A simple `make_unique`
template<typename T,typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

int main() {
    auto mat = make_unique<Matrix3D<double,200,27,200>>();
    (*mat)[199][26][199] = 10.0; // store 10.0 in the last element
}

Remember that this is C++11, and some compilers haven't yet implemented all the features I'm using. In particular the only released compiler I know of that supports template aliases (the using Matrix3D = line) is clang 3.0. The next version of GCC will support it though. Variadic templates (used to implement make_unique) are supported in both GCC and Clang, but not MSVC as of VS11.

Here's a version that uses only widely supported* C++11 features:

#include <memory>
#include <array>

template<typename T,size_t M,size_t N,size_t O>
struct Matrix3D {
    std::array<std::array<std::array<T,O>,N>,M> array;
};

// A simple `make_unique` that supports only zero-argument construction.
template<typename T>
std::unique_ptr<T> make_unique() {
    return std::unique_ptr<T>(new T);
}

int main() {
    auto mat = make_unique<Matrix3D<double,200,27,200>>();
    mat->array[199][26][199] = 10.0; // store 10.0 in the last element
}

* Widely supported means at least the latest releases of GCC, and MSVC.

bames53
  • 86,085
  • 15
  • 179
  • 244
  • The OP wants the matrix elements to be stored contiguously. Will the elements be stored contiguously with this solution? – Emile Cormier Mar 18 '12 at 17:35
  • @EmileCormier Yes, the elements are contiguous. – bames53 Mar 18 '12 at 17:38
  • According to this (http://stackoverflow.com/questions/8262963/stdarray-alignment), `std::array` can leave padding after the array elements. So `std::array` is not guaranteed to be contiguous. – Emile Cormier Mar 18 '12 at 17:50
  • It's not prohibited by the standard, but there's no reason to add padding. Any decent implementation will have `sizeof(std::array)` == `sizeof(T [N])` @EmileCormier – bames53 Mar 18 '12 at 18:02
  • 1
    That question was using tr1::array in some old implementation of libstdc++ which was adding extra alignment requirements for some reason. It may have been a bug, and it looks like it was fixed at least as far back as gcc 4.3.4. http://ideone.com/IX1JL @EmileCormier – bames53 Mar 18 '12 at 18:15
  • Could you please write a example code that builds a 200*27*200 matrix ?I'm not so familiar with c++. – peaceman Mar 18 '12 at 18:47
  • @JackJacky `std::unique_ptr> mat(new Matrix3D);` – bames53 Mar 18 '12 at 19:00
  • thank you but I couldn't use this I need complete code:#includeint main(){.....}please. – peaceman Mar 18 '12 at 19:04
  • @JackJacky I updated my answer with a couple whole programs, one of which I limited to C++11 features your compiler is likely to support. (you might want to note the compiler you're using in your question) – bames53 Mar 18 '12 at 19:51
1

If the dimensions are known at compile-time you can just allocate the object using new although I would probably put it int a structure because I keep mixing types up bewteen arrays and pointer (I barely ever use them directly):

struct array3d {
    double array[200][200][200];
};

std::auto_ptr<areay3d> array(new array3d);

Obviously, the array dimensions can become template arguments.

If the dimensions are determined only at run-time you'd need to allocate a contiguous array of double and do the array subscript computations yourself. This probably would also become a set if classes accessibg the elements: the subscript operator for the 3d array would return a reference to a 2d array, etc. std::valarray<double> is meant to help with this and there are boost classes for this as well.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • See this question on how to use a `std::valarray` to represent a contiguous 2D matrix: http://stackoverflow.com/questions/2187648/how-can-i-use-a-stdvalarray-to-store-manipulate-a-contiguous-2d-array – Emile Cormier Mar 18 '12 at 17:30
  • @RobertMason: indeed, it is. However, it is available for all contemporary C++ implementations unlike `std::unique_ptr` which is only available on systems which support rvalue references. For the purpose of automatically deleting an object both work equally well. Thus, the use of `std::auto_ptr` seems to be the more appropriate choice than `std::unique_ptr`. – Dietmar Kühl Mar 18 '12 at 20:12
1

You can write a wrapper around a std::vector and overload operator() to access matrix elements. The elements are stored contiguously in the 1D std::vector and operator() converts 3D indices into a 1D index into the std::vector. If the matrix was 2D, this is how the mapping from 2D to 1D would look like:

| 1 2 3 |
| 4 5 6 |  ---> [1 2 3 4 5 6 7 8 9] 
| 7 8 9 |

This ordering is called row major.

Here's an example of a class that overloads operator() to convert 3D indices into a row-major 1D index:

#include <iostream>
#include <vector>

template <class T>
class Matrix3D
{
public:
    Matrix3D(size_t m, size_t n, size_t o)
    : m_(m), n_(n), o_(o), data_(m*n*o) {}

    T& operator()(size_t i, size_t j, size_t k)
    {
        return data_[(i * n_ * o_) + (j * o_) + k];
    }

    const T& operator()(size_t i, size_t j, size_t k) const
    {
        return data_[(i * n_ * o_) + (j * o_) + k];
    }

private:
    std::vector<T> data_;
    size_t m_, n_, o_;
};

int main()
{
    Matrix3D<float> m(4, 3, 2);
    m(0,0,0) = 12.3f;
    m(3,2,1) = 45.6f;
    std::cout << m(0,0,0) << " " << m(3,2,1) << "\n";
}

The Boost.MultiArray library does essentially the same thing as this (and much more), but can be used for any dimension N.

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • Oops, that should be Boost.MultiArray, not Boost.MultiIndex. – Emile Cormier Mar 18 '12 at 17:40
  • It runs fine on my machine and on Ideone.com. I even tried using `vector::at` instead of `vector:operator[]`. Please note that indices are zero-based. – Emile Cormier Mar 18 '12 at 21:06
  • Works fine for me with `m(200,200,200)`. Are you sure you're using `<200` in your for loops and not `<=200`? Please use the debugger (or `printf`s) to pinpoint the line in the client code that causes the segfault and post the surrounding snippet as an edit to your question (or post a whole program that fails in Ideone.com). – Emile Cormier Mar 18 '12 at 21:31