11

My code:

#include <iostream>
#include <functional>
using namespace std;

struct A {
  A() = default;
  A(const A&) {
    cout << "copied A" << endl;
  }
};

void foo(A a) {}

int main(int argc, const char * argv[]) {
  std::function<void(A)> f = &foo;
  A a;
  f(a);
  return 0;
}

I'm seeing "copied A" twice on the console. Why is the object being copied twice, not once? How can I prevent this properly?

lucas clemente
  • 6,255
  • 8
  • 41
  • 61
  • Your code doesn't compile. What is `a`? – Kerrek SB Aug 10 '12 at 15:11
  • Sorry, deleted one line too much. – lucas clemente Aug 10 '12 at 15:12
  • 4
    As I said, I fixed it. This isn't homework, I'm just trying to learn C++11. – lucas clemente Aug 10 '12 at 15:15
  • 3
    [This](http://stackoverflow.com/questions/10008503/is-stdfunction-allowed-to-move-its-arguments) isn't a duplicate, but may help understand what's going on. – Luc Danton Aug 10 '12 at 15:18
  • OK disregard my previous comment; hadn't seen the update. I'm seeing it three times as well (with GCC) and only once if I call `foo` directly. What Luc Danton suggested makes sense, but doesn't account for the third copy. Maybe the order and number of calls to the copy constructor is somehow undefined? – smocking Aug 10 '12 at 15:28

2 Answers2

11

The specialization std::function<R(Args...)> has a call operator with the following declaration:

R operator()(Args...) const;

In your case, this means that the operator takes A. As such, calling f(a) results in a copy due to the pass-by-value semantics. However, the underlying foo target also accepts its argument by value. Thus there will be a second copy when the parameter to f is forwarded to foo.

This is by design, and in fact if A had a move constructor there would be only one copy followed by a move construction -- and calling f(std::move(a)) would only result in two move constructions. If you feel that two copies are too much you need to reconsider whether both foo and f should take A instead of e.g. A const&, and/or whether A can have a sensible move constructor.

You can also do std::function<void(A const&)> f = &foo; without modifying foo. But you should reserve that for the case where modifying foo is beyond your control, and/or making A cheaply move constructible is not an option. There is nothing wrong with passing by value in C++11, so I suggest that either both should take A, or both should take A const&.

Community
  • 1
  • 1
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • Interesting. Do you also happen to know why are some of us seeing it getting copied *three* times rather than just twice? I'm using GCC 4.4.6 with `-std=c++0x` (it's an outdated network-wide install). – smocking Aug 10 '12 at 17:01
  • 1
    @smocking I suspect it is a defect in the implementation. My reasoning is based on the specification for the call operator, which boils down to "*Effects:* `INVOKE(f, std::forward(args)..., R)` (20.8.2), where `f` is the target object (20.8.1) of `*this`." (there is also a *Returns:* clause but only for special-casing `void` returns). If I understand normative wording for the Standard library correctly then I don't think an implementation is allowed to copy/move more than passing to the call operator and the *INVOKE* operation require. – Luc Danton Aug 10 '12 at 17:13
  • On the other hand, the algorithms are very notoriously known to be allowed to freely copy their functor arguments. This is only mentioned as a *non-normative* note, that is to say, the approach seems to be 'what isn't forbidden is allowed' (actual words from the relevant resolution in the defect list). So consider that those two copies/moves are a minimum. I leave it to anyone to consider whether minimizing those copies/moves is a matter of QoI or conformance. – Luc Danton Aug 10 '12 at 18:22
  • Everything I do (when having move ctor also - VS11) I get at most only 1 copy. (even if everything is reference). I couldn't make it to have 2 moves – Ghita Jan 09 '13 at 18:44
  • I made it to have 2 moves actually. Before I was using std::bind to bind foo to function f. As it seems it's less efficient. – Ghita Jan 09 '13 at 18:48
1

It's being copied because you pass it by value. You could avoid all copies by passing it as a const reference instead.

Antimony
  • 37,781
  • 10
  • 100
  • 107