const static auto lambda used with capture by refe

2019-02-12 15:53发布

问题:

While using some local lambda objects in a C++11 function I was tempted to declare them as const static auto lambda = ... just to let the compiler know that there is just one std::function object needed (and possibly optimize the call and/or inline it) but I realized that capturing local values by reference in this circumstance leads to weird behavior.

Consider the following code:

void process(const Data& data, const std::function<void(DataElement&>& lambda) {
  ...
}

void SomeClass::doSomething()
{
  int foo = 0;

  const static auto lambda = [&foo] () { .... ++foo; .... }

  process(data, lambda);
}

This doesn't work with multiple invocations of doSomething() but the mechanics is not clear.

  • Is foo bound at the first invocation and then kept bound to a stack address which becomes invalid on successive invocations?
  • Am I forced so drop static in this circumstance?

Where is this behavior specified in the standard? Considering it's a static variable where is it constructed? Lazily on first invocation of doSomething() (so that the first invocation works) or at program start?

回答1:

A static function-scope variable is initialised "lazily," when control flow first reaches its declaration. This means that the capture by reference does indeed bind to the foo currently on stack, and when the call terminates, that binding becomes dangling.

Don't try to help the compiler too much; making lambda static looks like a micro-optimisation, with very bad side effects. There's next to no overhead involved in actually creating a closure object, and the compiler can easily inline it regardless of whether it's static or not.

Not to mention the fact that you're not saving on creating the std::function object even with your approach. The type of a lambda expression is an unnamed closure object, not std::function. So even if lambda is static, the std::function object is created in each call anyway (unless the whole thing is inlined).



回答2:

This doesn't work with multiple invocations of doSomething() but the mechanics is not clear.

This is because foo is allocated on the stack. The exact address of foo depends on the call stack that led to the invocation of doSomething. In other words, the address of foo is likely to be different between function invocations, unless the call stack is exactly the same.