3

I'd like to use lambda functions to asynchronously call a method on a reference counted object:

void RunAsync(const std::function<void()>& f) { /* ... */ }

SmartPtr<T> objPtr = ...
RunAsync([objPtr] { objPtr->Method(); });

Creating the lambda expression obviously creates a copy but I now have the problem that converting the lambda expression to a std::function object also creates a bunch of copies of my smart pointer and each copy increases the reference count.

The following code should demonstrate this behavior:

#include <functional>

struct C {
    C() {}
    C(const C& c) { ++s_copies; }

    void CallMe() const {}

    static int s_copies;
};

int C::s_copies = 0;

void Apply(const std::function<void()>& fct) { fct(); }

int main() {
    C c;
    std::function<void()> f0 = [c] { c.CallMe(); };
    Apply(f0);
    // s_copies = 4
}

While the amount of references goes back to normal afterwards, I'd like to prevent too many referencing operations for performance reasons. I'm not sure where all these copy operations come from.

Is there any way to achieve this with less copies of my smart pointer object?

Update: Compiler is Visual Studio 2010.

fschoenm
  • 1,391
  • 13
  • 32
  • Whats bad with a lot of copies? a copy of a smart pointer is a copy of two pointers and increment of a number... not something to kill performance – Daniel Nov 28 '11 at 16:20
  • 3
    Why don't you capture by reference? As in `[&c] { c.CallMe(); };` and same with smart pointer. ` – Nawaz Nov 28 '11 at 16:25
  • 2
    @Dani: In many reference-counted smart pointers, the arithmetic is atomic, and thus requires more overhead. – James McNellis Nov 28 '11 at 16:27
  • 1
    @Nawaz Hm, I wasn't sure if that was possible. What happens when the variable goes out of scope if it is only captured by reference? – fschoenm Nov 28 '11 at 16:28
  • 2
    Have you considered using a function template so that you can eschew `std::function` altogether? If you can make your lambda captureless (i.e., if you can find some other way to get `C` into the lambda, e.g. by using a parameter), you can use a function pointer. – James McNellis Nov 28 '11 at 16:29
  • @fschoenm: Then it'll break. But if the original variable goes out of scope, then you *should* make a copy of it, non? – Kerrek SB Nov 28 '11 at 16:32
  • @JamesMcNellis I'm not sure what you mean. – fschoenm Nov 28 '11 at 16:46
  • If you also implement a move constructor besides the copy constructor in your example, that will be used as well. – Some programmer dude Nov 28 '11 at 17:33
  • 2
    Which compiler are you using? MSVC2010 does not property implement default move constructors, so your lambda is only copyable. I suspect if you manually implemented the lambda, with move semantics, you'll see what you want (which is how a proper implementation of C++11 would act). – GManNickG Nov 28 '11 at 18:17
  • This smells of micro-optimization and premature optimization. You shouldn't *care* unless it's an actual performance problem. – Nicol Bolas Nov 28 '11 at 19:11

2 Answers2

5

std::function probably won't be as fast as a custom functor until compilers implement some serious special treatment of the simple cases.

But the reference-counting problem is symptomatic of copying when move is appropriate. As others have noted in the comments, MSVC doesn't properly implement move. The usage you've described requires only moving, not copying, so the reference count should never be touched.

If you can, try compiling with GCC and see if the issue goes away.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Unfortunately, I cannot compile the whole project in GCC but my demo code behaves as expected in GCC. So if I understand correctly the issue isn't the lambda expression but the ``std::function`` implementation? It appears as if I have the same (or worse) issue when using ``std::function`` with ``std::bind``. – fschoenm Nov 29 '11 at 08:23
  • Also, I used to have a custom functor implementation that I wanted to replace by ``std::function`` that doesn't show this behavior. Seems I can't get rid of it yet. – fschoenm Nov 29 '11 at 08:26
  • @fschoenm I don't use Windows, but James McNellis works at Microsoft, so you might try his suggestion in the comments. Try replacing the `std::function` with a raw function pointer, since lambda functions convert to that too. – Potatoswatter Nov 29 '11 at 14:36
2

Converting to a std::function should only make a move of the lambda. If this isn't what's done, then there's arguably a bug in the implementation or specification of std::function. In addition, in your above code, I can only see two copies of the original c, one to create the lambda and another to create the std::function from it. I don't see where the extra copy is coming from.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • I forgot to mention that I'm using Visual Studio 2010. In GCC I also get only two copies (one if I implement a move constructor). – fschoenm Nov 28 '11 at 22:16