6

Imagine you had a class hierarchy like the following:

class Robot
{
public:
    void OnTaskCompleted() {}

private:
    Task *m_pTask;
};

class Task
{
public:
    virtual void DoTask() = 0;
};

class TidyUp : public Task
{
public:
    void DoTask()
    {
        // When TidyUp task is compeleted invoke OnTaskCompleted() from here.
    }
};

I need to call OnTaskCompleted() from TidyUp::DoTask(). What would be the recommended way to do that?

I'd like to avoid:

  • making OnTaskCompleted() static
  • passing Robot pointer to Task
jpen
  • 2,129
  • 5
  • 32
  • 56

4 Answers4

5

The static route is not feasible unless there is only one Robot in your program, and an instance of that robot is available statically.

Passing a Robot to the task may be OK, but it might reveal too much information and prohibit task usages with objects other than robots.

A third alternative would be to make an interface-like class for completion notifications, extending it in the Robot, and calling it from the task. Unfortunately, C++ does not make it particularly easy by pushing you into the virtual inheritance territory.

You could adopt a callback approach that is common in POSIX thread libraries (passing a void pointer and a function pointer that takes a void pointer), but that is not too C++-ish.

Finally, if you are using C++11, you have anonymous functions that let you address the issue very gracefully by wrapping both a function and an object on which it operates in a single closure without using an external library, such as boost.

Here is a quick example of the third approach (link to ideone):

#include <iostream>
#include <string>
using namespace std;

class WithNotification {
public:
    virtual void notify()=0;
};

class Robot : public virtual WithNotification {
private:
    string name;
public:
    Robot(const string& n) : name(n) {}
    virtual void notify() {cout << name << " has been notified" << endl; }
};

class Task {
private:
    WithNotification& onFinished;
public:
    Task(WithNotification& f) : onFinished(f) {}
    void run() {
        cout << "The task is running" << endl;
        onFinished.notify();
    }
};

int main() {
    Robot r1("Quick");
    Robot r2("Brown");
    Task t1(r1);
    Task t2(r2);
    t1.run();
    t2.run();
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • @dasblinkenlight-I like the sound of your third option. Would you be able to show me an example for it please? – jpen Aug 08 '12 at 11:08
  • @dasblinkenlight-Thanks for that! +1 – jpen Aug 08 '12 at 11:27
  • And the reason for the virtual inheritance is...? – Christian Rau Aug 09 '12 at 09:40
  • @ChristianRau This lets you define classes later that inherit from `WithNotification` through multiple paths. For example, you can make a `Monkey` inherit `WithNotification`, and then a `Cyborg` that inherit both `Robot` and `Monkey`, if this somehow makes sense in your model. – Sergey Kalinichenko Aug 09 '12 at 12:13
  • @dasblinkenlight Ok, so the usual use-case of virtual inheritance. It's just that I don't see the neccessity here (and I try to avoid virtual inheritance as much as possible, but this may be just me). It's just that your answer seems to somehow imply the neccessity of virtual inheritance whenever working with abstract interfaces. – Christian Rau Aug 09 '12 at 12:52
0

I would go with passing a pointer. But if you plan to use Task not only with Robot consider making a TaskDelegate interface for sending notifications:

class TaskDelegate
{
public:
    virtual void onTaskFinished(Task *sender) = 0; //implement in successors

protected:
    ~TaskDelegate() {} //don't allow deletion via interface pointer
};

Now your Task class will look like:

class Task
{
    Task(TaskDelegate *delegate) {...}
    ...
};

Inherit Robot to be a delegate

class Robot : public TaskDelegate {...}

Thus the Task is able to notify any object that implements TaskDelegate interface

Andrew
  • 24,218
  • 13
  • 61
  • 90
0

There is no way of solving this without passing anything to the contained class. The easiest is to have Robot inherit from a pure virtual base class that contains the OnTaskCompleted method, and have the Task class have a pointer or reference to this virtual base class, and pass the Robot instance to the contained Task class. Instead of having the Robot class inherit from a base class, you could pass it as a template parameter to a templated Task class.

Another solution is to make Task a template class, and pass this->OnTaskCompleted function as template parameter. Something like:

template<class Callback>
class Task
{
public:
    void DoTask()
    {
        // ...

        Callback();
    }
};

class Robot
{
public:
    Robot()
    {
        m_pTask = new Task<this->OnTaskCompleted>;
    }

    // ...
};

If this last way works I actually have no idea, as I haven't tried it.

As you can see, both from my and other answers, there are many ways of doing this. None of them is actually an "official" or "recommended" way to do this. But almost all of these need you to pass either a class or an object around to the Task class.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
0

According to Xeo's suggestion I've done something like this:

#include <iostream>
#include <functional>

typedef std::tr1::function<void (int)> CallbackFunc;

class Task
{
public:
    Task(CallbackFunc callbackFunc) : m_callbackFunc(callbackFunc) {}
    virtual ~Task() {}
    virtual void DoTask() = 0;
protected:
    CallbackFunc m_callbackFunc; 
};

class TidyUp : public Task
{
public:
    TidyUp(CallbackFunc callbackFunc) : Task(callbackFunc) {}
    void DoTask() {
        std::cout << "I love tidying up!" << std::endl;
        m_callbackFunc(6); // When TidyUp task is compeleted invoke OnTaskCompleted() from here.
    }
};

class Robot : private Uncopyable
{
public:
    Robot() : m_pTask(new TidyUp(std::bind(&Robot::OnTaskCompleted, this, std::placeholders::_1))) {}
    ~Robot() { delete m_pTask; }
    void DoTask() { m_pTask->DoTask(); }
    void OnTaskCompleted(int nCleannessLevel) { std::cout << "Done!  Cleanness Level: " << nCleannessLevel << std::endl; }
private:
    Task *m_pTask;
};

int main()
{
    Robot robot;
    robot.DoTask();
    return 0;
}
jpen
  • 2,129
  • 5
  • 32
  • 56
  • You're disregarding the [rule of three](http://stackoverflow.com/q/4172722/500104) in your `Robot` class. If you ever make a copy, you'll have double-deletes. Also, applying the [Non-virtual Interface idiom](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface) would help ensuring that the callback is always called. – Xeo Aug 09 '12 at 08:18
  • @Xeo-I want my OnTaskCompleted() equivalent function to be private. I've made it private (it compiles fine and produces the same output result) but I'm now wondering if this is a valid design. I mean m_callbackFunc() can call into this private callback function. Is this a bad design? – jpen Aug 09 '12 at 15:47