Dear Readers!

In today’s post I’m sharing one of the peculiarities of connecting our own classes with existing libraries. Let’s say a library has a function that accepts callbacks and you have a class that has members this callback needs to operate with:

C++

1
2
3
4
5
// this function is part of OpenCV:
void cv::setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);

// which accepts a callback with the following signiture:
typedef void(* cv::MouseCallback) (int event, int x, int y, int flags, void *userdata);

Let’s also say you don’t want users to write their own callback for your class, because you feel it is your responsibility to make your class as usable as possible from the very beginning. This train of thought seems perfectly reasonable and as such you decide to provide a callback of your own. But how should you proceed with the implementation? One approach would be to make all needed members of the class public, which immediately allows you to access everything without accessor functions:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ImgPaint {
public:
    // ...

    cv::Mat mMainImg;
    
private:
    // ...
};

void paintCallback(int event, int x, int y, int flags, void* userdata)
{
    ImgPaint& paint = *(ImgPaint* )userdata;
    cv::Mat temp;
    cv::Mat& mainImg = paint.mMainImg;
    mainImg.copyTo(temp);
    
    if (event == cv::EVENT_LBUTTONDOWN) {
        //...
    }
}

Looking at this would immediately ring a few bells in non-absolute-beginner programmers, as exposing state to the world is usually considered to be bad practice. One way to correct this would be to hide mMainImg to the safety of privacy and write an accessor function. If the callback needs to operate on multiple members, one could use the same recipe to solve the problem:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ImgPaint {
public:
    // ...
    cv::Mat& getMainImg();
    cv::Point getPt1() const;
    cv::Point getPt2() const;
    // ...
    
private:
    cv::Mat mMainImg;
    cv::Point2i mPt1;
    cv::Point2i mPt2;
    // ...
};

But is it really a good solution? Of course, there is now an additional layer between the data and the callback, but is it that big of a difference? If we examine things a bit more closely, we realize that this solution pretty much gets us back to where we started: everything is exposed to the world and that additional layer we installed didn’t change a thing in the grand scheme of things. We also realize that we did all this for the sake of one funtion, which makes us feel even worse.

An obvious way to correct all this is to turn things around by moving the callback into the class itself instead of exposing state to it. This puts our member variables back into hiding and there is an added benefit of not needing to pass stuff in through the void pointer (and as such we can omit nasty casts):

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ImgPaint {
public:
    // ...
    void paintCallback(int event, int x, int y, int flags, void* userdata);
    
private:
    cv::Mat mMainImg;
    // ...
};

void ImgPaint::paintCallback(int event, int x, int y, int flags, void* userdata)
{
    cv::Mat temp;
    mainImg.copyTo(temp);
    
    if (event == cv::EVENT_LBUTTONDOWN) {
        //...
    }
}

Everything looks dandy, but as in many cases before, the compiler disagrees:

Make

1
2
3
4
5
6
7
8
9
10
11
./main.cpp: In function ‘int main()’:
./main.cpp:38:63: error: invalid use of non-static member function ‘void ImgPaint::mouseCallback(int, int, int, int, void*)’
     cv::setMouseCallback(appName, paint.mouseCallback, appName);
                                                               ^
In file included from ./main.cpp:1:0:
./ImgPaint.hpp:12:10: note: declared here
     void mouseCallback(int event, int x, int y, int flags, void* userdata);
          ^~~~~~~~~~~~~
make[2]: *** [CMakeFiles/tutprog.dir/build.make:63: CMakeFiles/tutprog.dir/main.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:68: CMakeFiles/tutprog.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

Hmm. It would seem that using non-static member functions is not allowed in this fashion, probably because of the implicit first parameter (pointing to this) that is appended to the parameter list (which in turn changes the function signiture and makes it unsuitable for our purposes). Of course, one could get rid of the implicit parameter by borrowing some Java wisdom and declaring the callback and all variables static, but after thinking a bit, one stumbles upon a few problems:

  • Although static member variables can be declared private, they still need to be defined in a cpp file, where they will become visible to everybody (though only class members have access to them). This partially defeats our efforts for encapsulation, which fact alone encourages us to abandon this solution.
  • Static member functions cannot be overloaded without needing to modify the header (admittedly though, this is less of a problem for callbacks because of their strict requirements).

Fortunately enough, C++ provides a mechanism for cases exactly like this, which mechanism easily solves most of our problems in a simple, yet elegant way:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// header file
void paintCallback(int event, int x, int y, int flags, void* userdata);

class ImgPaint {
public:
    // ...
    friend void paintCallback(int event, int x, int y, int flags, void* userdata);
    
private:
    cv::Mat mMainImg;
    // ...
};



// source file
void paintCallback(int event, int x, int y, int flags, void* userdata)
{
    ImgPaint& paint = *(ImgPaint* )userdata;
    cv::Mat temp;
    cv::Mat& mainImg = paint.mMainImg;
    mainImg.copyTo(temp);
    
    if (event == cv::EVENT_LBUTTONDOWN) {
        //...
    }
}

By simply forward declaring the callback in the header and making it a friend, we immediately gain access to everyting a class has to offer without exposing anything in any fashion, although the need for passing in stuff through a void pointer doesn’t disappear. I guess we have to make do.

So, the lesson of the day is as follows:

Probably the best way of providing callbacks for your classes is to make the callback a friend of your class.

As always, thanks for reading.