0
votes

I am trying to come up with the most efficient (i.e. minimal amount of copies) implementation of the following. I have a queue which is used to execute std::function<void()> objects at some later point in time. The queue can only take trivial data types and thus I need to pass it a pointer to a std::function rather than the std::function itself (i.e. the queue holds std::function<void()>* types).

Ideally, if a piece of data is captured (by value) in a lambda, only a SINGLE copy of this data should be made throughout the process of creating an std::function and adding it to the queue.

Here is an example I have been playing with:

std::function<void()>* invoke(const std::function<void()>& fn) {
    printf("invoke()\r\n");
    return new std::function<void()>(fn);
}

which I would use like so

class Base {
public:
    virtual void sayHello() const = 0;
};

class A : public Base {
public:
    A() {
        printf("A::A()\r\n");
    }
    A(const A& a) {
        printf("A copy constructor\r\n");
    }
    A(const A&& a) {
        printf("A const move constructor\r\n");
    }
    A(A&& a) {
        printf("A move constructor\r\n");
    }
    A& operator=(A&& other)
    {
        printf("A move assignment operator\r\n");
        return other;
    }

    void sayHello() const override { printf("A says hello\r\n"); }
};

int main() {
    A myA;
    
    printf("invoking lambda which calls myA.sayHello()\r\n");
    std::function<void()> *fn = invoke([myA](){
        myA.sayHello();
    });

    return 0;
}

Since the lambda is capturing the object (myA) by value, a copy is made when the lambda is originally created. Now, since the lambda you see in main() is "temporary" (only used in the call to invoke) there should really only ever by a single copy of the lambda hanging around and thus only a single (additional) copy of myA.

However, here is an example of my output:

invoking lambda which calls myA.sayHello()
A copy constructor
A move constructor
invoke()
A copy constructor

It appears that, when I create a new std::function on the heap, it is COPYING the lambda (and thus myA) instead of just moving it, which is what my desired behavior is since the original lambda and std::function (the one created automatically when passing the lambda to the invoke function) are only temporary anyways.

I feel like I am very close, but am misunderstanding something here. Could anyone help?

Thanks!

EDIT

Based upon all the discussion here, I have made a few modifications to my code. Now I am stepping this up to a more complicated example as follows:

//Same A and Base classes as above

inline static void invoke(std::function<void()> fn) {
    printf("invoke(std::function<void()> fn)\r\n");

    std::function<void()>* newF = new std::function<void()>(std::move(fn));

    (*newF)();
    delete newF;
    printf("return\r\n");
}
int main() {
    printf("Starting Tests...\r\n");
    
    A myA;

    invoke([myA](){
        myA.sayHello();
    });
    return 0;
}

Which has the following, unexpected output:

Starting Tests...
        A::A()
        A copy constructor
        A move constructor
invoke(std::function<void()> fn)
        A says hello
        A::~A()
return
        A::~A()
        A::~A()

I can't figure out why there is a 3rd call to the destructor since only 2 copies of the A object should be in existence (the original copy in main() and the copy made by the copy constructor in the lambda).

2
Do you think that the amount of time you will gain, from attempting this micro-optimization, will come anywhere close to the time you've already spent trying to figure this out?Sam Varshavchik
Your function is receiving const std::function<void()>& fn, then doing std::move(fn). You can't move from a const reference. Did you perhaps mean to accept it by value? Or r-value reference? Or plain reference? Or just about anything aside from const (reference or not)?ShadowRanger
@ShadowRanger I didn't realize that was the case. Regardless, if I remove the std::move, the copy is still made.Patrick Wright
Right, because if you don't move, you get a copy.NathanOliver
@PatrickWright He didn't mean you should remove the std::move, you should make invoke take it's argument by value.super

2 Answers

1
votes

Your example does not work because you are trying to std::move from a const reference. This will result in a type std::function<...> const&&, but the move constructor of std::function only accepts std::function<...>&& (without the const). This is not unusual in C++, as const rvalue references are a weird corner case of the language that don't make much sense conceptually. In particular, you cannot reasonably move from a const rvalue reference, because you won't be allowed to make any changes to the source.

If the intention is to guarantee that no copies happen, you should accept the argument via rvalue reference:

std::function<void()>* invoke(std::function<void()>&& fn) {
    printf("invoke()\r\n");
    return new std::function<void()>(std::move(fn));
}

Note that this restricts the function to only work with rvalue arguments. If you prefer a more flexible design, consider max66's answer.

0
votes

Maybe... using perfect forwarding you can transform a copy in a move.

template <typename F>
auto invoke (F && fn) {
    printf("invoke()\r\n");
    return new std::function<void()>(std::forward<F>(fn));
}

You get

A::A()
invoking lambda which calls myA.sayHello()
A copy constructor
invoke()
A move constructor
A move constructor

Unrequested Suggestion: avoid like the plague direct memory allocation management; use smart pointer instead

template <typename F>
auto invoke (F && fn) {
    printf("invoke()\r\n");
    return std::unique_ptr<std::function<void()>>
     {new std::function<void()>(std::forward<F>(fn))};
}