Boost::Bind and virtual function overloads: why do they work?
Asked Answered
L

2

11

I wrote some code and got scared that it will not work - so I wrote a prototype:

#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>

class base {
private:
    boost::function<void (int)> action;
protected:
    virtual void onDataBaseReady(int i) { std::cout << i << std::endl; }
public:
    void call() {
        action(10);
    }

    base() {
        action = boost::bind(&base::onDataBaseReady, this, _1);
    }
};

class child : public base {
protected:
    virtual void onDataBaseReady(int i) { std::cout << i+10 << std::endl; }
};

int main()
{
    static child c;
    c.call();
    std::cin.get();
    return 0;
}

that compiles and works. (outputs 20). But Why? Also I tested under VS2010 and wonder if it would work across platforms (say compiled under GCC)?

Mainly action = boost::bind(&base::onDataBaseReady, this, _1); scares me - we say &base::...

Lymphocyte answered 29/4, 2013 at 15:39 Comment(1)
we say &base::... and we point to a function with polymorphic behavior. It would scare me if it did not call the final overrider!Skivvy
E
16

A pointer to a virtual method does a virtual function lookup when called.

#include <iostream>
#include <memory>

struct base {
  virtual void foo() { std::cout << "base\n"; }
  virtual ~base() {}
};

struct derived:base {
  void foo() override final { std::cout << "derived\n"; }
};

int main() {
  void (base::*mem_ptr)() = &base::foo;
  std::unique_ptr<base> d( new derived() );
  base* b = d.get();
  (b->*mem_ptr)();
}

so, it "just works". The member function pointer (this->*&base::foo)() is not the same as a fully qualified function call this->base::foo(). The first is a way to store the foo part of calling this->foo(), the second is a way to skip virtual method lookup and directly call base::foo.

Erymanthus answered 29/4, 2013 at 15:40 Comment(1)
@idmean sorry, I have religious objections against owning raw pointers.Erymanthus
Q
1

Mainly action = boost::bind(&base::onDataBaseReady, this, _1); scares me - we say &base::...

It would actually be much more scary if it performed static dispatch, rather than dynamic dispatch. Consider this simple example:

struct base {
   virtual void foo() { /* maintain some invariants */ }
};
struct derived : base {
   virtual void foo() { /* maintain different invariants */ }
};

And then consider that you bind the function at the parent and call it on the derived object. The implementor of derived knows what invariants apply to the derived type, which might be the same, a subset or completely different than the invariants in the base type.

void apply(base & b) {
   std::bind(&base::foo, &b)();
}

If dispatch was resolved at binding time, and the functor was applied to the derived type (of which you might not know the exact type!) then the invariants of the derived type might be broken. In the context of the apply function it is impossible to know what the object really is, or what the invariants of that type are, so what you probably want to do is let dynamic dispatch do its magic.

[That is from a high level design point of view, without even going into the detail that you cannot use a pointer to member to perform static dispatch...]

Quirk answered 29/4, 2013 at 17:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.