?

Log in

No account? Create an account
 

Callback for something that might disappear - Advanced C++ Community

About Callback for something that might disappear

Previous Entry Callback for something that might disappear Oct. 21st, 2007 @ 01:31 am Next Entry

I wanted to have a fairly robust callback that would call something that might disappear before the callback is triggered. I wanted the callback to simply go dead when its target disappeared.

I thought about it a lot, and I came up with this:


#include <boost/function.hpp>
#include <boost/intrusive_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/lambda/core.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
#include <iostream>

using ::std::cout;

class existence_guardmaster {
   class guard_t;
   class isvalid_wrapper;
   friend class isvalid_wrapper;
   friend void intrusive_ptr_add_ref(guard_t *);
   friend void intrusive_ptr_release(guard_t *);
   typedef ::boost::intrusive_ptr<guard_t> guardptr_t;

 public:
   typedef ::boost::function<void (void)> wrapped_t;

   existence_guardmaster(bool startvalid = true);
   ~existence_guardmaster();

   bool isvalid();
   void setinvalid();
   void setvalid();

   const wrapped_t wrap_with_guard(const wrapped_t &func) const;

 private:
   const guardptr_t guard_;
};

class existence_guardmaster::guard_t {
   friend class existence_guardmaster;
   friend void intrusive_ptr_add_ref(guard_t *);
   friend void intrusive_ptr_release(guard_t *);
   bool valid_;
   unsigned int refs_;

   guard_t(bool startvalid) : valid_(startvalid), refs_(0) { }
   bool isvalid() const {
      return(valid_);
   }
   void setinvalid() {
      valid_ = false;
   }
   void setvalid() {
      valid_ = true;
   }
};

inline existence_guardmaster::existence_guardmaster(bool startvalid)
     : guard_(new guard_t(startvalid))
{
}

inline existence_guardmaster::~existence_guardmaster()
{
   guard_->setinvalid();
}

inline bool existence_guardmaster::isvalid()
{
   return guard_->isvalid();
}

inline void existence_guardmaster::setinvalid()
{
   guard_->setinvalid();
}

inline void existence_guardmaster::setvalid()
{
   guard_->setvalid();
}

class existence_guardmaster::isvalid_wrapper {
 public:
   typedef void result_type;
   isvalid_wrapper(const guardptr_t &ptr, const wrapped_t &func)
        : ptr_(ptr), func_(func)
   {
   }
   void operator ()() const { if (ptr_->isvalid()) func_(); }
 private:
   const guardptr_t ptr_;
   const wrapped_t func_;
};

inline const existence_guardmaster::wrapped_t
existence_guardmaster::wrap_with_guard(const wrapped_t &func) const
{
   return isvalid_wrapper(guard_, func);
}

inline void intrusive_ptr_add_ref(existence_guardmaster::guard_t *guardp)
{
   ++(guardp->refs_);
}

void intrusive_ptr_release(existence_guardmaster::guard_t *guard)
{
   if ((guard->refs_ <= 0) || (--(guard->refs_) <= 0)) {
      delete guard;
   }
}

static inline void thefunction()
{
   ::std::cout << "Called!\n";
}

int main(int argc, char *argv[])
{
   existence_guardmaster master;
   existence_guardmaster::wrapped_t wf = master.wrap_with_guard(thefunction);
   wf();
   master.setinvalid();
   wf();
}

This results in a lot of overhead per call though. More than I was expecting. And the other problem is that callback functions are limited to functions with a certain signature. You could use boost::bind to bind all the arguments to your callback and store them away safely so it looked like it was void (void), but that seems inelegant and like it wouldn't work well for some situations.

It's pretty easy to do f(g(x)) in boost, but not so easy to do f(g)(x) when f is something that creates a function that largely just needs to pass things along to g.

Is there a way around this?

Current Mood: contemplativecontemplative
Leave a comment
[User Picture Icon]
From:stridera
Date:October 21st, 2007 10:32 am (UTC)
(Link)
You could always do a pointer to the function. I agree that a wrapper class is safer, but the pointer would have less overhead, and more room for failure.
int (*ptrfunc)() = NULL;

int disappearingfunc() { cout << "boo" << endl;  return 0; }

... in main ...
if (*ptrFunc) 
 int result = (*ptrfunc)();
else
  cout << "Does not exist" << endl;

ptrfunc = &disappearingfunc;

if (*ptrFunc) 
 int result = (*ptrfunc)();
else
  cout << "Does not exist" << endl;


Expected results:
Does not exist
boo



[User Picture Icon]
From:stridera
Date:October 21st, 2007 10:34 am (UTC)
(Link)
Looking over the code, it looks like you intend to have a pointer to a function... ignore my suggestion. That's what I get for checking this right as I wake up. Sorry. :/
[User Picture Icon]
From:ataxi
Date:October 21st, 2007 11:39 am (UTC)
(Link)
It seems like the solution really boils down to
if (ptr_->isvalid()) func_();
Or more generally
if (various checks I wanted to make when I performed this action, 
    and therefore bundled with this action, are satisfied)
  perform this action;
And the problem is that you want to protect your future uses of the functor from all the bug-prone coupling implicit in the required state-checking.

To be honest for a case of the simplicity of your test code I'd be pretty happy to go with just checking manually. But if the lifetimes of your objects do have non-trivial overhangs you could look at boost::weak_ptr for "locking" a reference to a shared data structure until you're finished with it, or establishing it's gone for good if it already has.

(I don't get why you used intrusive_ptr in the above, by the way? Normally shared_ptr is the more pleasant option. Unless you have a need specific to other code you're working with.)

It would be cool to use boost::lambda to generate constraints, such as "my_dynamic_data != 0" "on the fly" and push them into a complete list of predicates/preconditions to be checked before function application. I think bind(_1 != 0, my_dynamic_data) would express one such.

I believe the proposed support for variadic templates in C++0x would solve the argument forwarding problem for you. isvalid_wrapper could then be templated on variadic arguments corresponding to the arg list of its operator().

As it is you could solve that problem the way I think libraries like boost::function currently do, using macro trickery to generate the code for the cases up to a certain finite number of args.
[User Picture Icon]
From:omnifarious
Date:October 21st, 2007 02:35 pm (UTC)
(Link)

You've gotten my intent. And yes, the test case is silly and trivial. :-)

The problem with using boost::weak_ptr is that it constrains the object that may disappear to being a heap allocated object that everybody points to with boost::shared_ptr. In the context I was envisioning using this for, an event driven cooperative threading and IO system, this isn't actually the case for any of the objects that would generally needed to be guarded in this fashion. I could change it so it is, but I would rather not.

I used boost::intrusive_ptr because I like it better as a solution. It reduces calls to new and it improves locality by putting the reference count near the data it's a reference count of. Of course, I really should measure how much those things matter in a non-trivial program. :-) I think of boost::shared_ptr as the thing to use when making boost::intrusive_ptr work would be nearly impossible or seriously contorted.

Your idea of using boost::lambda in this way is interesting. :-)

I was surprised that boost::function didn't seem to expose any way of getting at the argument list. I would've sort've expected that it had the argument list as a cons list or something. Especially after going to all that work to tease it out in the first place. If it had I might've been able to use it and some clever calls to bind to construct a wrapper function that worked for any type.

[User Picture Icon]
From:ataxi
Date:October 21st, 2007 09:39 pm (UTC)
(Link)
"I think of boost::shared_ptr as the thing to use when making boost::intrusive_ptr work would be nearly impossible or seriously contorted."

I think most other people follow the opposite regimen and use shared_ptr by default for reference counted data structures. It's a pretty lazy habit sometimes, though ... overuse shared_ptrs and it starts to feel like you're coding in a GC-ed language ;-)

In order to work with boost::function the way you're describing in that last paragraph, you'd have to use template metaprogramming. It's possible you could do a TMP for-each on an arguments typelist to bind arguments one by one to your target function, but I personally wouldn't attempt it. Seems to me like it'd be over-engineering for the sake of the personal challenge ...

Raw function pointers themselves can be held in any pointer format (e.g. void (*FuncType)()) and then brutally re-cast to their actual signature at point of use, based on type information carried with them in a container format. Not exactly type safe, though!

One other thing I'm not sure I get entirely is why, if your guarded functions rely on other objects that may or may not be valid, they don't check their preconditions internally.

Another approach you could take is to create a "safe" version of a function, call it once, and then have it "thunk" itself to an "unsafe" (and fast!) version permanently or temporarily based on having verified its preconds.
[User Picture Icon]
From:omnifarious
Date:October 22nd, 2007 01:57 am (UTC)
(Link)

*chuckle* Yes, reference counting everything is nice. But it's also easy to get yourself in trouble with it by accidentally having circular data structures. Though it served Python well enough early on.

One other thing I'm not sure I get entirely is why, if your guarded functions rely on other objects that may or may not be valid, they don't check their preconditions internally.

The basic idea is that I have an event handling queue. Anything can shove events onto the queue, but the result of processing the events may be the destruction of some things. For example, the chat protocol handler may get an client disconnect message, which means it will destroy all the objects associated with that client. But there may be events sitting in the queue already for the file descriptor or upper layers of that handler. It can't really find them all and kill them, so it has to leave behind a marker that it's gone now and can't receive the messages. In this case the function being called is almost certainly a member function.

Another approach you could take is to create a "safe" version of a function, call it once, and then have it "thunk" itself to an "unsafe" (and fast!) version permanently or temporarily based on having verified its preconds.

Except there's no place to keep the state needed. The existence_guard is there as much for the book keeping of having the special little object to store the fact of existence or non-existence in as anything else.

(Deleted comment)
[User Picture Icon]
From:omnifarious
Date:October 22nd, 2007 10:58 pm (UTC)
(Link)

It would be interesting to generalize what I did in the manner you describe. I'm not sure I'll tackle that task though as I have a specific use in mind for what I just made. :-)

(Leave a comment)
Top of Page Powered by LiveJournal.com