6
\$\begingroup\$

I want to fill an array of complex numbers using a uniform generator. I thought up the next code. (Complex is a simple fixed-point complex data type.)

std::generate(inputData.begin(), inputData.end(), []()-> Complex {
    static std::default_random_engine generator;
    static std::normal_distribution<double> distribution(0.0, 0.5); // mean = 0.0, stddev = 0.5
    return Complex(distribution(generator), distribution(generator));
});

So I though up using static variables within the lambda expression. Is that inefficient? Alternatively I would create them outside of the lambda, and put them on the capture list. But I don't need them outside of the lambda, so this seems cleaner to me.

\$\endgroup\$
  • \$\begingroup\$ When you say "Uniformly distributed", do you mean "Uniform distribution of probabiliies" or "Uniform distribution of outcomes", because only one of those is random. \$\endgroup\$ – Kaz Feb 16 '17 at 13:06
  • \$\begingroup\$ Ok, the question is not about the random variables. Those are working for me. The question is if it is inefficient to make generator and distribution static, instead making local variables and including them via the lambda capture-list via reference. \$\endgroup\$ – JHBonarius Feb 16 '17 at 13:25
  • \$\begingroup\$ I personally don't like putting static variables into a lambda function, but many C++ modernists will say that this is a good coding practice. I prefer to use function-objects for these cases. \$\endgroup\$ – ncalmbeblpaicr0011 Feb 16 '17 at 16:21
  • 1
    \$\begingroup\$ Note the existence of std::complex \$\endgroup\$ – Justin Feb 16 '17 at 22:47
  • 1
    \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Vogel612 Feb 17 '17 at 9:47
3
\$\begingroup\$

The problem with this is that it leaks memory, although not in the traditional sense of leaking.

Function static variables live till the end of the program. So while generator and distribution are only created when this code is first run, they only get destroyed at the end of your program even though you never use them later. This eats up memory the entire time.

On the other hand, the amount of memory it eats up is pretty small and constant, so for almost all purposes, it shouldn't make too big of a difference. Even if you repeatedly called a function that had this code in it, the static variable would live and not be recreated. Just don't use this in a way that generates the lambda 100s of times, such as in intense template metaprogramming.


In C++17, if you were using this to initialize a big enough vector, you could make use of execution policies to parallelize this like so:

std::generate(std::execution::par_unseq, inputData.begin(), inputData.end(), []()-> Complex {
    thread_local std::default_random_engine generator; // thread_local so we don't have to do any locking
    thread_local std::normal_distribution<double> distribution(0.0, 0.5); // mean = 0.0, stddev = 0.5
    return Complex(distribution(generator), distribution(generator));
});

This has the added benefit that these variables will be destructed when the threads used finish, although when that happens is mostly unclear.

\$\endgroup\$
  • \$\begingroup\$ Yes, I thought of statics living until the end of the program. I was worried that because of the lambda, the statics would live until the end of the function. I.e. that this would be very inefficient as the constructor of the variables would be called for every lambda evaluation. \$\endgroup\$ – JHBonarius Feb 17 '17 at 8:27
0
\$\begingroup\$

In answer to Justin's suggestion:

So the problem with static variables is that they only get destroyed when the program is terminated. For my example it is not really a problem. But scoping them would probably be cleaner. E.g.:

{
    std::default_random_engine generator;
    std::normal_distribution<double> distribution(0.0, 0.5); // mean = 0.0, stddev = 0.5
    std::generate(referenceData.begin(), referenceData.end(),
        [&generator, &distribution]()-> Complex
    {
        return Complex(distribution(generator), distribution(generator));
    });
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.