Delegating to boost::function with arbitrary signature

432 views Asked by At

I'm trying to create something like a boost::function, but with an extra 'smart handle' tucked inside, which controls the lifetime of a resource the functions needs to execute correctly.

If I didn't need the function signature to be completely generic, this would be easy. I would just create a functor with the correct signature, and keep a boost::function and the handle as members:

class library_function
{
public:
    library_function(
        library_handle handle,
        boost::function<result_type(arg1_type, arg2_type)> function)
        :
    m_handle(handle),
    m_function(function)
    {}

    typename result_type operator()(arg1_type arg1, arg2_type arg2)
    {
        return m_function(arg1, arg2);
    }

private:
    boost::function<result_type(arg1_type, arg2_type)> m_function;
    library_handle m_library;
};

However, I do need generic signatures. The code below is as close as I've got but gets a compile error, presumably, because I'm not able to unpack a list of arguments like this:

template<typename Signature>
class library_function
{
public:
    library_function(
        library_handle handle,
        boost::function<result_type(arg1_type, arg2_type)> function)
        :
    m_handle(handle),
    m_function(function)
    {}

    typename result_type operator()(arg1_type arg1, arg2_type arg2)
    {
        return m_function(arg1, arg2);
    }

    // I don't think I can declare the 'params' like this ...
    typename boost::function_types::result_type<Signature>::type operator()(
        boost::function_types::parameter_types<Signature> params)
    {
        return m_function(params); // ... nor unpack them like this
    }

private:
    boost::function<Signature> m_function;
    library_handle m_library;
};

I think params is an MPL list, so what I've actually done is declare a call operator with a single MPL-list parameter. My MPL-foo is not good. Is there a way to convert the list to a legal definition of multiple function paramters?

Alternatively, is there a way to get boost::function to accept this single MPL-list and unpack it into its, possibly multi-parameter callable?


For those who are interested, the 'smart handle' is a Windows DLL from which a function had been loaded by name (GetProcAddress). The handle will free the DLL when it's destroyed but that mustn't happen while anyone still wants to call the function. Hence embedding it in the callable.

2

There are 2 answers

0
Jamboree On BEST ANSWER

For plain function, you can define the library_function as:

template<typename Signature>
class library_function
{
public:
    library_function(
        library_handle lib, Signature* library_function)
        :
    m_library(lib),
    m_function(library_function)
   {}

    operator Signature*() const { return m_function; }

private:
   library_handle m_library;
   Signature* m_function;
};

The trick is operator Signature*() conversion operator, which allows the compiler fallback to the plain function m_function when you call with library_function.

And you can add a helper function like:

template<typename Signature>
boost::function<Signature> make_library_function(library_handle library, Signature* f)
{
    return library_function<Signature>(library, f);
}

Since boost::function then manages a copy of library_function, the 'smart handle' is managed as well.

2
James On

Hopefully it is OK that I can answer how to accomplish your use case without answering how to write your generic function type. You can do what you need to do with boost::function and boost::bind and boost::shared_ptr which is easier than writing the code you're attempting. Unless I'm missing a use case you have in mind, there is not much reason to write a specialized library_function type.

Here is some code that does what you are asking using an int pointer as a resource, but you could apply this pattern to any types you want. This is C++11 code but only in as much I am using its std::bind, std::function, and std::shared_ptr. You can replace those C++11 std functionality directly with the boost types and it should work. (I used C++11 so you can see it run here: http://ideone.com/yIsg1E

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

using namespace std;
// Called by the shared_ptr to 'delete' the pointer.
struct cleanup {
    void operator() (int *i) {
        std::cout << "Cleaning up resource: " << i << "(" << *i << ")"
                  << std::endl; 
        // Do the work for releasing resource
        delete i;
    }
};

void use_temporary_resource(std::shared_ptr<int> resource,
                            const int &proof, const int &multiple) {
    int &i = *resource;
    std::cout << "Proof " << proof << ", using the resource: " << i << std::endl;
    std::cout << i << "*" << multiple << "=" << i*multiple << std::endl;
}

int main() {
    // Notice that an instance of cleanup() is passed to the shared_pointer construction
    std::shared_ptr<int> tempResource1(new int(1), cleanup());
    std::cout << "Resource 1 created: " 
              << tempResource1.get() << "(" << *tempResource1 << ")" << std::endl;

    // Resource is held onto with bind
    std::function<void(int i)> proof1 = std::bind(use_temporary_resource,
                                                  tempResource1,
                                                  1,
                                                  std::placeholders::_1);
    // If it wasn't this would close the resource
    tempResource1.reset();
    proof1(1);
    proof1(2); 
    {
    std::shared_ptr<int> tempResource2(new int(2), cleanup());
    // Resource is held onto with bind
    std::function<void(int i)> proof2 = std::bind(use_temporary_resource,
                                                    tempResource2,
                                                    2,
                                                    std::placeholders::_1);
    // If it wasn't this would close the resource
    tempResource2.reset();
    proof2(10);
    proof2(20);
    }  // Once scope goes away from proof2, the resource is released.
    std::cout << "Resource 2 should have been released after exiting scope containing proof2 (here)."
              << std::endl;

    std::shared_ptr<int> tempResource3(new int(3), cleanup());
        std::cout << "Resource 3 created: " 
                  << tempResource3.get() << "(" << *tempResource3 << ")" << std::endl;
    // Show that normally a resource will disapear if not placed in bind
    tempResource3.reset();
    std::cout << "Exiting..."  << std::endl; 
    return 0;
}

This is the output showing that resource management is being bound to scopes of the std::function:

Resource 1 created: 0x94b7008(1)
Proof 1, using the resource: 1
1*1=1
Proof 1, using the resource: 1
1*2=2
Proof 2, using the resource: 2
2*10=20
Proof 2, using the resource: 2
2*20=40
Cleaning up resource: 0x94b7048(2)
Resource 2 should have been released after exiting scope containing proof2 (here).
Resource 3 created: 0x94b7048(3)
Cleaning up resource: 0x94b7048(3)
Exiting...
Cleaning up resource: 0x94b7008(1)

In summary, you simply create a shared_pointer with a custom deleter for the resource you want to scope. Then you bind that shared_pointer to your function which requires it using bind. You don't actually have to use the parameter in your function, it is enough that the shared_pointer is passed by value. After that, as long as the bound function is in scope the resource itself will be in scope even if the place you created the shared_ptr originally has left scope.

I hope this helps!