简体   繁体   中英

Abstracting pointer-to-member-function: safety and alternatives

In this problem, assume that we have handled all pointers in a nice, careful manner - to prevent question bloat I don't want to include my cleanup code here!

Let's say we have two classes, Foo and Bar with class definitions as follows:

class Foo
{
    public:
        Foo();
        void fooFn();
};

class Bar
{
    public:
        Bar();
        void barFn();
};

Assume that it is necessary that Foo and Bar have no inheritance relationship, but we need to call both fooFn and barFn in response to some stimulus. We can create a controller class with a container from which to call fooFn and barFn on specific instances of Foo and Bar - a static std::vector for example - but we run into an issue: pointers to member functions of Foo and Bar instances are of different types.

By using a static vector< std::function<void()>* > in the controller class, we can make a workaround. Foo and Bar instances can have a function which adds pointers to the vector through a lambda function which captures this :

void Foo::registerFnPointer()
{
    ControllerClass::function_vector.push_back( new [this](){ return this->fooFn(); } );
}

I have tested this method, and it appears to work without any problems. That said, I am concerned about the issues that could be caused by circumventing the type difference mentioned before... Am I worrying over nothing? Is there a better way to accomplish equivalent functionality?

The only problem I see has actually nothing to do with the functors but has to do with object lifetime. That is: I'm not sure how you ensure that you always de-register the functors registered with ControllerClass whenever an Foo or Bar instance gets destroyed.

You mention however that you do proper memory management.

In my opinion you do not need to store a pointer to function<void()> , you can simply store function as value (that is have a vector<function<void()>> ).

Prior to C++11 and lambdas, to achieve the same effect you would have used a (boost) function also but you would would have used boost::bind with with the address of the fooFn and the first parameter bound to a pointer (or reference) to the Foo object instance.

This would have created an instance of the function that holds all of the information needed to call the fooFn method on the given object. You could then store the instance in a vector to call it at a later time (and had the same problem of making sure no boost::function bound to a destroyed object remains registered)

Edit:

For the sake of completeness, the link to the Boost bind documentation specific for binding members: http://www.boost.org/doc/libs/1_56_0/libs/bind/bind.html#with_member_pointers

What you are doing is actually quite similar only that you are now using a lambda to capture the object pointer and to define the function to be called.

So I see no problem with what you are doing (other then the one I already mentioned).

You could use an adapter class. This might be overkill for what you're doing, but it may work.

The benefits of doing it this way are:

  1. You don't have to change the original classes. Creating void Foo::registerFnPointer() is ugly.

  2. You don't have to use your static std::vector .

  3. You don't have to deal with function pointers.

So let's say you have two different classes like this:

struct Foo
{
    void fooFn () { 
        std::cout << "Foo::fooFn ()" "\n" ; 
    }
};

struct Bar
{
    void barFn () {
        std::cout << "Bar::barFn ()" "\n" ; 
    }
};

The goal is to put them into a container and call their respective *Fn () member-functions.

An adapter would look something like this:

struct Adapter_Base
{
    virtual ~Adapter_Base () {} ;

    virtual void adapterFn () = 0 ;
};

template <typename T>
struct Adapter : Adapter_Base
{
    T tVal ;

    Adapter (const T &tVal) : tVal (tVal) {}
    void adapterFn () ;
};

template <>
void Adapter <Foo>::adapterFn ()
{
    tVal.fooFn () ;
}

template <>
void Adapter <Bar>::adapterFn ()
{
    tVal.barFn () ;
}

And you could use it like this:

int main ()
{
    std::vector <std::unique_ptr <Adapter_Base> > v1 ;

    std::unique_ptr <Adapter_Base> u1 (new Adapter <Foo> (Foo ())) ;
    std::unique_ptr <Adapter_Base> u2 (new Adapter <Bar> (Bar ())) ;

    v1.push_back (std::move (u1)) ;
    v1.push_back (std::move (u2)) ;

    for (auto &adapter : v1) {
        adapter->adapterFn () ;
    }

    return 0 ;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM