Search code examples
c++boostboost-multi-array

How to properly initialize a boost multi_array of objects?


I have been surprised to find that boost::multi_array seems to allocate its initial elements differently from, say, std::vector. It does not seem to fill each element with a unique element (using its default value or default constructor). I'm having trouble finding more information about this.

Is there a way to make the multi_array fill itself with a unique object at each element?

For example, consider the following:

static int num = 0;

struct A {
   int n;
   A() : n((::num)++) {
      std::cout << "A()" << std::endl;
   }
   virtual ~A() {}

   void print() {
      std::cout << "n=" << n << std::endl;
   }
};

int main() {
   std::cout << "vector:" << std::endl;
   std::vector<A> v(3);
   for (auto x : v) {
      x.print();
   }

   std::cout << "multi:" << std::endl;
   boost::multi_array<A, 2> m(boost::extents[2][2]);
   for (auto x : m) {
      for (auto y : x) {
         y.print();
      }
   }
}

This results in the output:

vector:
A()
A()
A()
n=0
n=1
n=2
multi:
A()
n=3
n=3
n=3
n=3

Why is the constructor called only once for the multi_array? How can the multi_array be "filled out" with unique objects (using A's default constructor)?


Solution

  • To quickly fill the whole array do something like fill_n¹:

    std::fill_n(a.data(), a.num_elements(), 0);
    

    With boost multi_array you can use a view to your own memory buffer to get the same performance (std::uninitialized_copy is your friend). (actually, you could even map an array view on existing memory, and you want to keep the existing values).

    I've written a comparative demo about this here: pointers to a class in dynamically allocated boost multi_array, not compiling

    Live On Coliru

    #include <boost/multi_array.hpp>
    #include <type_traits>
    #include <memory>
    
    struct octreenode { int a; int b; };
    
    class world {
    public:
        world(double x, double y, double z, int widtheast, int widthnorth, int height)
                : 
                    originx(x), originy(y), originz(z), 
                    chunkseast(widtheast), chunksnorth(widthnorth), chunksup(height)
        {
    #define OPTION 4
    
    #if OPTION == 1
            static_assert(std::is_trivially_destructible<octreenode>::value, "assumption made");
            //std::uninitialized_fill_n(chunk.data(), chunk.num_elements(), octreenode {1, 72});
            std::fill_n(chunk.data(), chunk.num_elements(), octreenode {1, 72});
    #elif OPTION == 2
            for(auto a:chunk) for(auto b:a) for(auto&c:b) c = octreenode{1, 72};
    #elif OPTION == 3
            for (index cz = 0; cz < chunksnorth; ++cz) {
                for (index cx = 0; cx < chunkseast; ++cx) {
                    for (index cy = 0; cy < chunksup; ++cy) {
                        chunk[cz][cx][cy] = octreenode{1, 72};
                    }
                }
            }
    #elif OPTION == 4
            static_assert(std::is_trivially_destructible<octreenode>::value, "assumption made");
            for (index cz = 0; cz < chunksnorth; ++cz) {
                for (index cx = 0; cx < chunkseast; ++cx) {
                    for (index cy = 0; cy < chunksup; ++cy) {
                        new (&chunk[cz][cx][cy]) octreenode{1, 72};
                    }
                }
            }
    #endif
            (void) originx, (void) originy, (void) originz, (void) chunksup, (void) chunkseast, (void) chunksnorth;
        }
    
    private:
        double originx, originy, originz;
        int chunkseast, chunksnorth, chunksup;
    
    #if 1
        typedef boost::multi_array<octreenode, 3> planetchunkarray; // a boost_multi for chunks
        typedef planetchunkarray::index index;
        planetchunkarray chunk{boost::extents[chunksnorth][chunkseast][chunksup]};
    #else
        static_assert(boost::is_trivially_destructible<octreenode>::value, "assumption made");
    
        std::unique_ptr<octreenode[]> raw { new octreenode[chunksnorth*chunkseast*chunksup] };
        typedef boost::multi_array_ref<octreenode, 3> planetchunkarray;
        typedef planetchunkarray::index index;
        planetchunkarray chunk{raw.get(), boost::extents[chunksnorth][chunkseast][chunksup]};
    #endif
    };
    
    int main() {
        world w(1,2,3,4,5,6);
    }
    

    The variant using multi_array_ref is an example of how to avoid copy-constructing the elements (it's akin to the optimization used by std::vector when it uses uninitialized memory for reserved but unused elements).


    ¹ Of course for unique values, use std::iota or std::generate