1

This question relates to the DelayedCaller in this answer.

The DelayedCaller binds a function pointer and its arguments and works like a charm as long as the the arguments aren't pointers to things shorter-lived than the DelayedCaller's execution (think string.c_str() of a local variable as argument).

To circumvent this issue I created a storage for problematic arguments handled by a template function scanning the arguments.

What I need now is kinda the opposite: I want the member function to be called on a different object of the same type, by evaluating the the address of the pointer given to the DelayedCaller as an argument.

I currently see two ways to go about this:

  1. std::placeholders: Instead of providing the object when creating the DelayedCaller it is provided with the call method.
  2. a wrapper for the object pointer that dereferences twice (overloading ->).

I favor 2. over 1. (I don't want to have to provide an argument whenever using call()), but there might other options I am not even considering.

Example:

#include <iostream>
#include <string>
#include <functional>
#include <memory>

class MyClass
{
    float myFloat;
public:
    MyClass(float f):myFloat(f){}

    void myFunc(int arg1)
    {
        std::cout << arg1 << ", " << myFloat << '\n';
    }
};

class MyContainer
{
public:
    MyClass* object;
};

// DelayedCaller implementation
class DelayedCaller
{
public:
    template <typename TFunction, typename... TArgs>
    static std::shared_ptr<DelayedCaller> setup(TFunction&& a_func,
                                                TArgs&&... a_args)
    {
        return std::shared_ptr<DelayedCaller>(new DelayedCaller(
            std::bind(std::forward<TFunction>(a_func),
                      std::forward<TArgs>(a_args)...)));
    }
    void call() const { func_(); }

private:
    using func_type = std::function<void()>;
    DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
    func_type func_;
};

int main()
{
    MyContainer container;
    MyClass* c1 = new MyClass(45.6);
    container.object = c1;
    // the next line is the critical one. Instead of myFunc being called
    // on the current value of container.object, it should be called on
    // the one container.object is holding when caller is called.
    auto caller(DelayedCaller::setup(&MyClass::myFunc, container.object, 123));

    caller->call();

    MyClass* c2 = new MyClass(22.8);
    container.object = c2;
    caller->call();

    delete c1;
    delete c2;

    return 0;
}
Phyr
  • 47
  • 4

3 Answers3

0

How about leaving "binding" to C++ compiler with usage of lambda expression?

auto caller(DelayedCaller::setup([&container] { container.object->myFunc(123);}));

Gives the output:

123, 45.6
123, 22.8

PS. This lambda is directly convertible into std::function<void(void)>, thus DelayedCaller constructor could be public and created without setup function if wanted. If you can edit its implementation of course.

R2RT
  • 2,061
  • 15
  • 25
  • This would work fine, if I didn't have to make use of aforementioned storage of parameters. Most of the member functions in use take c-strings as parameters which are retrieved from local variables. By the time caller is called, the local variables have been freed. I catch this via template parameters which are modified for parameters that are not move-constructable like c-strings and then forwarded to DelayedCaller, ensuring their validity when the call is made. I don't see how I can make this work with lambda expressions. – Phyr Dec 14 '17 at 15:30
  • @Phyr Could you share minimal code example of such use case? As for me there is no logical difference between std::bind and lambda except that lambda's syntax is nicer. Also, once you operate on c-strings there are two cases possible - you operate on compile constant c-strings and dynamic ones (usually wrapped by std::string), where first has no ownership and the latter can and must be moved into ownership of functor (std::function, lambda or std::bind). – R2RT Dec 14 '17 at 15:58
  • After delving more into lambda expressions I am now seeing how I can make it work. I can pass the big stable std::string I have by capturing its reference and then using something like substr(x,y) followed by c_str(). I could also capture just the substring with an appropriate local variable. With data members of bigger but shorter lived objects, extracting the data members as to not capture the whole object by copy requires a bit of setup but it does solve my problem in a more general way (as it allows tighter control over how it is captured). +1 – Phyr Dec 15 '17 at 10:06
0

std::reference_wrapper might help, use:

auto caller(DelayedCaller::setup(&MyClass::myFunc, std::ref(container.object), 123));

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • This solves my problem with the least amount of changes. But I recommend R2RT's answer as an overall nicer solution. – Phyr Dec 15 '17 at 10:09
0

Your really need to understand what std::bind does.

When using std::bind, it will copy(or move respectively if they are respectively r-value, but as a reminder, whether they are moved depend on callable objects and each arguments feed into std::bind respectively)

Next, I'll discuss the situation when there's only one argument, but the rule of it applies to situation where there're more than one arguments respectively.

Both the callable object(which could be pointers to functions or functors) and the arguments into the object of std::function returned by std::bind, so it will never occur the case you asked.

If the argument is not a pointer, or reference, you can just feed the (temporary) objects into sts::bind and the c++ standard promises that it will only be copied or moved, not by reference unless you use std::ref or std::cref to wrap that object.

But if you feed std::unique_ptr or other things that wraps a pointer(or reference as shown in above), you need to consider them as a pointer(reference), not an object(although copy/move choices still occurs)

If the argument is a pointer, then the pointer will be copied into it.

Will, in this case, if the pointer points to a local variable, bingo, then you have one bug to track.

Actually the code you write will work well if (the destructor of the object does not do any damage to the object in the memory, in other word, the object exists after it is destructed, like POD and etc) && (the memory contains the object is not reused by any other objects, when sth like this (another function is called and its local variables occupies the memory of that object and write something to it) happens it is reused).

If the pointer points to sth you allocated on heap and it is not deallocated until your call of the DelayedCaller::cal is done, then there's nothing wrong with you code.

If the pointer is a constant pointer that points to a literal, then it will be all fine.

The case when the argument is a reference is roughly the same as the case when it's a pointer.

Reference: http://en.cppreference.com/w/cpp/utility/functional/bind

JiaHao Xu
  • 2,452
  • 16
  • 31