1

Following up my previous question some months ago, I would like to ask how a container internally handle the lampda expression.

Assuming following piece of code:

#include <iostream>
#include <type_traits>
#include <queue>
#include <functional>

std::queue<std::function<void()>> funcs;

class A
{
    public:
      A()
      {
        std::cout << "A constructor" << std::endl;
      }
      void foo(int a)
      {
      }
};

class ThePool
{
   public:
    template <typename _Callable, typename Object, typename... _Args>
    void QueueFunction(_Callable __f, Object& obj, _Args... __args)
    {
        funcs.push([__f, &obj, &__args...]() mutable
            {
                (obj.*__f)(__args...);
            });
    }
};

int main(int argc, char** argv)
{
    ThePool t;
    A a;
    int x = 5;
    t.QueueFunction(&A::foo, a, x);
    std::function<void()> func = funcs.back();
    func();
    return 0;
}

If pass an instance of class A and args as value it is clear that the values are copied. I confuse what happens if I pass the instance of class A as reference and args as reference. I know that internally in a container copy takes place. From my point view the address of the function pointer will be copied. Are the instance of the obj and args be copied as well?

Doing an example I notice that when I passed the A instance as reference and finally extract the function f for the queue and call it the instance of A and this are not the same.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
getsoubl
  • 808
  • 10
  • 25
  • 3
    Mandatory read: https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier – Passer By Aug 22 '18 at 09:05
  • In lambda expression you are creating a copy of the object. So "&" in `QueueFunction` has no actual effect (this was not your intention) – Marek R Aug 22 '18 at 09:09
  • You are right.I modified the example. My question is what happens if I pass obj and __args by reference. Printing the address of a and address of this are differrent. – getsoubl Aug 22 '18 at 09:14

1 Answers1

1

The container is indeed free to copy its elements. Further, std::function has as requirement that the stored functor is CopyConstructible, which it certainly makes use of. Therefore, you have to choose:

  • If you pass objects by value, you don't really have to worry about object lifetimes. However, calling member functions on copies of the object (or with copied parameters) may not be what you want.

  • If you pass objects by (some form of) reference (e.g. wrapping them in std::ref) you will never create copies of the objects and parameters, but they might go out of scope before the queued member function is called.

Currently In your original code, you are creating a copy of obj in the lambda (obj is a Obj& but that doesn't matter to the lambda capture - it copies if not explicitly capturing by reference). To instead keep a reference in the lambda, capture obj by reference ([&obj]), not by value. Alternatively, pass your objects wrapped in std::ref.

As for the parameters, they are currently all copied (both in QueueFunction as well as the lambda capture). If that's not your intention, see above.

If you store references, then all the copy operations mentioned at the start will just copy the reference (not the object).

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • You second point is correct . That I have in my mind But when I changed the obj to &obj inside push I expected that the address of a and this to be equal with this. But this is not true . The addresses are different. – getsoubl Aug 22 '18 at 09:26
  • @GeorgeTsoumplekas [Cannot reproduce](http://coliru.stacked-crooked.com/a/1c11328d33962912). Can you make sure that the object is not destroyed before the stored function is called? Also, which `this` are you referring to? It would really help if the problem you describe could be demonstrated in your [MCVE]. – Max Langhof Aug 22 '18 at 09:36