Search code examples
c++functionboostboost-multi-array

Is safe boost::multi_array_ref returned by a function?


I'm trying to use boost::multi_array_ref to use a block of contiguous data in my code. But I worry about if I use the code like below, the 1d C array won't be confirmed to save:

#include <iostream>
#include "boost/multi_array.hpp"
boost::multi_array_ptr<double, 2> test(int N,int c,int d) {
    double *data = new double[N];
    boost::multi_array_ref<double, 2> a(data, boost::extents[c][d]);
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            a[i][j] = 10 * i + j;
        }
    }
    return a;
}
int main()
{
    boost::multi_array_ptr<double, 2> a = test(100000000,10000,10000);
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            std::cout << a[i][j] << std::endl;
        }
    }
}

The result is right and I want to use std::unique_ptr to replace the C style array, but the Visual Studio told me:

no instance of constructor "boost::multi_array_ref<T, NumDims>::multi_array_ref [with T=double, NumDims=2Ui64]" matches the argument list   

I don't know which kind of parameter boost::multi_array_ref need. Why the std::unique_ptr can't be use as parameter but C style array can? Can I use C style array without worry? Is safe boost::multi_array_ref returned by a function?

I'm sure that it will cause memory leak,but how to solve it?


Solution

  • double *data = new double[N];
    

    That's a raw pointer and no-one owns the allocation. You're correct that it leads to memory leaks. However, since you want to include ownership, why use multi_array_ref?

    Live On Compiler Explorer

    #include <algorithm>
    #include <numeric>
    #include "boost/multi_array.hpp"
    
    auto test(int c, int d) {
        boost::multi_array<double, 2> a(boost::extents[c][d]);
        std::iota(a.data(), a.data()+a.num_elements(), 0);
        return a;
    }
    
    #include <fmt/ranges.h>
    int main()
    {
        boost::multi_array<double, 2> a = test(4, 5);
    
        fmt::print("{}\n", a);
        fmt::print("{}\n", test(3, 6));
        fmt::print("{}\n", test(6, 2));
    }
    

    Prints:

    [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]
    [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17]]
    [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11]]
    

    And has no memory leaks.

    If you need the multi-array-ref:

    You might hack your way around with owning smart pointers:

    using ARef    = boost::multi_array_ref<double, 2>;
    using ARefPtr = boost::shared_ptr<ARef>;
    

    Now you can allocate the data like you did before - but without the leak:

    auto test(int c, int d) {
        auto data = boost::shared_ptr<double[]>(new double[c*d]);
    

    See this post for details and compatibility with std::shared_ptr

    And Use the aliasing constructor to share ownership of data while changing the apparent element type to ARef:

        auto a = ARefPtr(data, new ARef(data.get(), boost::extents[c][d]));
        std::iota(a->data(), a->data() + a->num_elements(), 0);
        return a;
    }
    
    #include <fmt/ranges.h>
    int main()
    {
        ARefPtr a = test(4, 5);
    
        fmt::print("{}\n", *a);
        fmt::print("{}\n", *test(3, 6));
        fmt::print("{}\n", *test(6, 2));
    }
    

    Of course, now you're dealing with smart pointers, so you need more de-references. See it Live:

    [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]
    [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17]]
    [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11]]