1

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?

  • 2
    My advice, don't use nested vectors. Write a class that wraps a 1d vector and overload the `operator()` for indexing. That makes creating a zeroed out structure very easy. You can see a toy example of this here: https://stackoverflow.com/questions/43358369/c-n-nested-vectors-at-runtime/43358434#43358434 – NathanOliver Mar 25 '21 at 15:01
  • 1
    @NathanOliver or overload `[]` through proxies to get a syntax resembling multi-d vectors. – SergeyA Mar 25 '21 at 15:07
  • Or that. requires more plumbing but it does make it look more natural. – NathanOliver Mar 25 '21 at 15:08
  • Mandatory disclaimer: [Boost.MultiArray](https://www.boost.org/doc/libs/1_75_0/libs/multi_array/doc/user.html) exists. – SergeyA Mar 25 '21 at 15:12
  • Thanks for the useful advice, I may have a play around with these options. – Alex Rossiter Mar 25 '21 at 15:35

2 Answers2

1

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];
 }
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

You could use resize() and a couple for loops to set bar. It's not the prettiest solution, but it should have pretty good performance as no temporaries are create and there are no unnecessary assignments. That would look like

bar.resize(ni);
for(auto& twodee : bar)
{
    twodee.resize(nj);
    for(auto& onedee : twodee)
        onedee.resize(nk);
}

And now bar has the same size and is filled with zeros.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402