Search code examples
c++vectorconstructorinitializationstdvector

Is there an efficient way to initialise the size of a multidimensional vector when only known at runtime?


I have a class that has a 3D vector as one of it's variables. This size of this vector won't be known until runtime. Is there an efficient way to initialise this vector?

For example, my class may be

class Foo {
public: 
    std::vector<std::vector<std::vector<float>>> x;
    std::vector<std::vector<std::vector<float>>> y;
    std::vector<std::vector<std::vector<float>>> z;

    std::vector<std::vector<std::vector<float>>> bar;

    int ni;
    int nj;
    int nk;
}

with a constructor

Foo::Foo(std::vector<std::vector<std::vector<float>>> x_,
         std::vector<std::vector<std::vector<float>>> y_,
         std::vector<std::vector<std::vector<float>>> z_) {
    x = x_;
    y = y_;
    z = z_;

    ni = x.size();
    nj = x[0].size();
    nk = x[0][0].size();

    std::vector<std::vector<std::vector<float>>> tmp(ni, std::vector<std::vector<float>>(nj, std::vector<float>(nk)));
    bar = tmp;
}

Can I do the last two lines of the above without having to assign the dummy variable tmp?


Solution

  • This is how you could do it (but don't miss to read the end):

    #include <vector>
    
    class Foo {
    public: 
        std::vector<std::vector<std::vector<float>>> x;
        std::vector<std::vector<std::vector<float>>> y;
        std::vector<std::vector<std::vector<float>>> z;
    
        int ni;
        int nj;
        int nk;
    
        using inner_type = std::vector<float>;
        using middle_type = std::vector<inner_type>;
        using outer_type = std::vector<middle_type>;
    
        outer_type bar;
    
        Foo(outer_type x_,
            outer_type y_,
            outer_type z_) :
             x(x_),y(y_),z(z_),
             ni(x.size()),
             nj(ni ? x[0].size() : 0),
             nk(nj ? x[0].size() : 0),
             bar( outer_type(ni,middle_type(nj,inner_type(nk))))
        {
        }
    };
    

    Members are initialized before the constructor body is executed, thats why I used the member initializer list. And I changed the order of the members, because members are initialized in the order they appear in the class definition. The access to x[0] made me a bit nervous, so I tried to make sure empty vectors don't cause havoc.

    This works and does what you want (I hope), but the vectors are populated with copies of the temporaries passed to their constructor, which isn't quite efficient. As an alternative you can resize the member as suggested in this answer.


    Last not least, reconsider if you really want a std::vector<std::vector<std::vector<float>>>. If you need all "rows" to have same number of "columns" then a nested vector makes you pay for something you do not use. Moreover, the most attractive feature of std::vector is its memory-locality. Though, the floats in a std::vector<std::vector<float>> are stored in fragmented areas of memory (as the elements are not stored directly in the vector).

    A flat std::vector<float> with appropriate index transformation is often the better choice.

     float& access_element(size_t i, size_t j, size_t k) {
           return bar[ i *offset_i + j*offset_j + k];
     }