Avoid manual creation of lambda to wrap the call to new[]: to be used as a generator function in std::generate

107 Views Asked by At

Short Version

Currently, I manually create a lambda function for the new [], as follows:

[](){ return new double[3]; }

This lambda is later used as a generator function in the call to std::generate:

std::generate(vec.begin(), vec.end(), [](){ return new double[3]; });

Can I avoid the creation of this lambda and use some more standard functionality/syntax sugar/alternatives? C++17 is preferred; however, if C++20 (or some proposal) offers something better, I am very interested as well.

The long version provides some background, and an existing solution for a similar situation, when for_each is used to deallocate the memory.


Long Version with Background

Need

  1. Create std::vector that stores double* pointers, which point to 1-D arrays of fixed size.
  2. Pass this structure to the library function that will fill it with data.
  3. Use the filled data.
  4. Clear the allocated memory.

Conditions

  • Due to (2), I do not have control over the type of argument for the library function; thus, I am limited to using std::vector<double*> no matter how other containers might be better suited.
  • Want clean, minimalistic, and efficient code
  • In particular, I started from simple plain for-loops and decided to go for STL <algorithm>.

Initial code

#include <vector>

int main()
{
    // (1)
    size_t numElems = 10;
    std::vector<double*> vec(numElems);
    for (size_t i=0; i<numElems; i++)
    {
        vec[i] = new double[3];
    }

    // (2)-(3) SOME ACTIVITY with vec

    // (4)
    for (size_t i=0; i<numElems; i++)
    {
        delete [] vec[i];
    }
}

where, for simplicity, I hardcoded the numElems in the vector vec, and used hardcoded size of the double* array to be 3. The passing to external function and activity in (2)–(3) are skipped. This code works.

New code

I tried the following:

#include <vector>
#include <algorithm>
#include <memory>

int main()
{
    // (1)
    size_t numElems = 10;
    std::vector<double*> vec(numElems);
    std::generate(vec.begin(), vec.end(), [](){ return new double[3]; });

    // (2)-(3) SOME ACTIVITY with vec

    // (4)
    std::for_each(vec.begin(), vec.end(), std::default_delete<double[]>());
}

which also works fine.

Question

In the revised version, the deletion is performed via std::default_delete<> applied via std::for_each that conveniently provides "a function representation" of the language keyword delete [] (a bit sloppy wording).

Is there a similar way "to represent" new []? Or am I stuck creating the lambda manually to be used as a generator function argument for std::generate?

2

There are 2 best solutions below

1
On BEST ANSWER

The following:

#include <type_traits>
#include <vector>
#include <algorithm>
#include <memory>

template<typename T>
struct default_new {
    typename std::decay<T>::type operator() (){ return new T; }
};

int main() {
    size_t numElems = 10;
    std::vector<double*> vec(numElems);
    std::generate(vec.begin(), vec.end(), default_new<double[3]>());

    // (2)-(3) SOME ACTIVITY with vec

    // (4)
    std::for_each(vec.begin(), vec.end(), std::default_delete<double[]>());
}

works. I do not know if such is provided from standard library, I believe not.

3
On

A single heap allocation costs a few pointers worth per element in memory overhead. So if you're dealing with an enormous vector of pointers, it might save memory to have a single allocation, and a vector as a view:

struct vector3 {
    vector3(size_t numElements) 
        // use new[] over std::make_unique to avoid value initialization,
        // since it's assumed the library function will initialize the data
        : storage(new std::array<double, 3>[numElements]) {
        view.reserve(numElements);
        std::transform(
            storage.get(), 
            storage.get() + numElements, 
            std::back_inserter(view),
            [](auto& a) { return a.data(); }); // no good substitute for lambda here
    }
    std::unique_ptr<std::array<double, 3>[]> storage;
    std::vector<double*> view;
};

usage:

vector3 myVector(10);
library_function(myVector.view);

Demo: https://godbolt.org/z/6x4PEs

EDIT: moved into class to guarantee lifetimes per Swift - Friday Pie's suggestion.