Sign in to follow this  
markgame66

Examples on C++ callbacks for a template class

Recommended Posts

Hi All, I've looked around on the net and whilst there are plenty of examples I can't find a page which shows clearly (to me at least) what I need to do to create a callback from my template class to another class. Basically what I have is a template class which holds generic data structures, and another class which has a specific instance of the template class. I want to be able to save the data and figured that if I can somehow create a call back from the template class that would work nicly. Just can't figure out how to do it. Any examples/help would be great. Thanks Mark

Share this post


Link to post
Share on other sites
i can't really understand what you want to do.

do you want something like a class that can call any method from any instance of any type?

something like this??:

Callback a,b;
b.set(MyInstance,&MyType::MyMethod);// MyInstance is of MyType which can anything
a=b;
a();// will call MyInstance.MyMethod();

Share this post


Link to post
Share on other sites
Sorry for the generic names, but it's hard to give concrete ones without more context.

This is how I'd do it:


#include <string>
#include <iostream>

template <typename T>
class Holder {
public:
class Callbackable {
public:
virtual void Callback(const T& t) = 0;
};

Holder(const T& t, Callbackable& callback) : t(t), callback(callback) {}

void DoCallback() {
callback.Callback(t);
}

private:
T t;
Callbackable& callback;
};

class AnotherClass : public Holder<std::string>::Callbackable {
public:
AnotherClass() : holder("Quack!", *this) {}

virtual void Callback(const std::string& t) {
std::cout << "AnotherClass::Callback: " << t << "\n";
}

void DemoTheCallback() {
holder.DoCallback();
}

private:
Holder<std::string> holder;
};

int main() {
AnotherClass ac;
ac.DemoTheCallback();
}



The class that wants to have the callback inherits from an interface (Holder<T>::Callbackable), and defines the callback. It can then pass *this into Holder<T>'s constructor as the class instance with the callback.

Share this post


Link to post
Share on other sites
I have something like:

<code>
template <class T> class map
{

void forEachElement(ProcessMethod pPethod)
{
for (Map::iterator it = m_map.begin(); it != m_map.end(); it++)
{
pMethod(it->second);
}
}

};

</code>

and what I want is to be able to have a instance of map in another class say X, but be able to call forEachElement, passing it a method from X, which will be responsible for the specific processing type.

Thanks

Mark

Share this post


Link to post
Share on other sites
Try a function pointer.


#include <string>
#include <iostream>

template <typename T>
class map {
public:
map(const T& a, const T& b, const T& c) : a(a), b(b), c(c) {}

template <typename C>
void forEachElement(C& inst, void (C::*callback)(const T&)) {
// example use of callback
(inst.*callback)(a);
(inst.*callback)(b);
(inst.*callback)(c);
}

private:
T a, b, c;
};

class X {
public:
X() : m("foo", "bar", "baz") {}

void callback(const std::string& t) {
std::cout << "X::callback: " << t << "\n";
}

void demoTheCallback() {
m.forEachElement(*this, &X::callback);
}

private:
map<std::string> m;
};

int main() {
X x;
x.demoTheCallback();
}



Alternatively, you could do it like the STL, and pass in a function object.

(I hope you're not rewriting std::map!)

Share this post


Link to post
Share on other sites
Here you go:

Callback.h:

class __iCallback
{
private:
inline __iCallback()
{
RefCount = 0;
}
private:
virtual void __CallMethod()=0;
private:
unsigned int RefCount;
private:
friend class Callback;
template <class T>
friend class __CallBack;
};
class Callback
{
public:
Callback();
~Callback();
Callback(const Callback &other);
Callback(__iCallback *prv);
public:
void operator = (const Callback &other);
void operator = (__iCallback *prv);
void Detach();
inline bool isEmpty()const
{
return (method == 0);
}
public:
inline void exec()
{
if(method)
method->__CallMethod();
}
inline void operator()()
{
if(method)
method->__CallMethod();
}
//------------------------------------------------
private:
__iCallback *method;
};
//------------------------------------------------
//------------------------------------------------
template <class T>
class __CallBack : public __iCallback
{
private:
typedef void (T::*CallProcedure)();
public:
__CallBack(T * s ,void (T::*m)())
:source(s),method(m)
{}
public:
virtual void __CallMethod()
{
(source->*method)();
}
private:
T * source;
CallProcedure method;
private:
friend class Callback;
};

//----------------------------------
template <typename OBJ>
inline __iCallback* callback(OBJ * what, void(OBJ::*method)())
{
return new __CallBack<OBJ>(what,&method);
}






Callback.cpp


Callback::Callback()
: method(0)
{
}
Callback::~Callback()
{
if(method)
{
method->RefCount--;
if(method->RefCount==0)
delete method;
}
}
Callback::Callback(const Callback& other)
:method(other.method)
{
method->RefCount++;
}
Callback::Callback(__iCallback* prv)
{
method = prv;
method->RefCount++;
}
void Callback::Detach()
{
if(method)
{
method->RefCount--;
if(method->RefCount==0)
delete method;
}
method = 0;
}
//---------------------------------------------
void Callback::operator =(const Callback& other)
{
if(method)
{
method->RefCount--;
if(method->RefCount==0)
delete method;
}
method = other.method;
method->RefCount++;
}
void Callback::operator = (__iCallback* prv)
{
if(method)
{
method->RefCount--;
if(method->RefCount==0)
delete method;
}
method = prv;
method->RefCount++;
}






Usage:
Callback m;// this object will call any function from any object
m = callback(InstanceOfObject,&TypeOfObject::MethodToCall);// this is how you associate m with an instance and a method
m() or m.exec()// is how you call the method, equivalent with InstanceOfObject->MethodToCall();

It is easy to modify this so that you can call methods with arguments.

Raxvan.

Edit:
your example now is:

X yourInstance;
pPethod = callback(yourInstance,&X::Method);

void forEachElement(Callback pPethod)
{
for (Map::iterator it = m_map.begin(); it != m_map.end(); it++)
{
pMethod(it->second);// suppose that you added support for arguments
}
}

Share this post


Link to post
Share on other sites
mattd, I've had a go with your example in my app, getting a compilation error:

error C2784: 'void C3DHashtable<T>::forEachElement(C &,void (__thiscall C::* )(const T & ))' : could not deduce template argument for 'void (__thiscall T1::* )(const T & )' from 'void (__thiscall X::* )(const GridCell &)'
with
[
T=GridCell *
]
and
[
T=GridCell *
]


Can you see where I've got this wrong. An abridged version of my code below (I'm not 100% about the functionality, but is does at least exibit the same compilation problems.

Thanks

Mark



#include "stdafx.h"
#include <string>
#include <iostream>
#include <map>
#include <algorithm>
#include <vector>

using namespace std;

static const C_numberOfDimensions = 3;

class C3DHashtableKey
{
public:

C3DHashtableKey(const int iX, const int iY, const int iZ)
{
m_iCoord[0] = iX;
m_iCoord[1] = iY;
m_iCoord[2] = iZ;
}

bool operator<(const C3DHashtableKey & rhs) const
{
return std::lexicographical_compare(m_iCoord, m_iCoord + C_numberOfDimensions, rhs.m_iCoord, rhs.m_iCoord + C_numberOfDimensions);
}

/* bool operator==(const C3DHashtableKey & rhs) const
{
return std::equal_range(m_iCoord, m_iCoord + C_numberOfDimensions, rhs.m_iCoord, rhs.m_iCoord + C_numberOfDimensions);
}
*/


private:

int m_iCoord[C_numberOfDimensions];

};

template<class T> class C3DHashtable
{

private:

std::map<C3DHashtableKey, T> m_map;

public:

C3DHashtable(void)
{
}

~C3DHashtable(void)
{
}

void Insert(const int x, const int y, const int z, T value)
{
m_map.insert(std::make_pair(C3DHashtableKey(x, y, z), value));
}

template <class C>
void forEachElement(C& inst, void (C::*callback)(const T&))
{
for (std::map<C3DHashtableKey, T>::iterator it = m_map.begin(); it != m_map.end(); it++)
{
(inst.*callback)(<C3DHashtableKey, T>);
}

}

size_t getNoOfMapElements()
{
return m_map.size();
}

};


struct GridCell
{
// Alternatice constructor for basic setting up of a cell for path finding
GridCell( double X, double Y, long Z)
{
m_X = X;
m_Y = Y;
m_Z = Z;
}

double m_X;
double m_Y;
double m_Z;
};


template <typename T>
class map {
public:
map(const T& a, const T& b, const T& c) : a(a), b(b), c(c) {}

template <typename C>
void forEachElement(C& inst, void (C::*callback)(const T&)) {
// example use of callback
(inst.*callback)(a);
(inst.*callback)(b);
(inst.*callback)(c);
}

private:
T a, b, c;
};

class X {

public:
X()
{
Map2.Insert(1,2,3, new GridCell(1,2,3));
Map2.Insert(4,5,6, new GridCell(4,5,6));
Map2.Insert(7,8,9, new GridCell(7,8,9));
}

//: m("foo", "bar", "baz") {}

void callback(const GridCell& t)
{

}

void demoTheCallback()
{
Map2.forEachElement(*this, &X::callback);
}

private:
C3DHashtable<GridCell*> Map2;
};

int main() {
X x;
x.demoTheCallback();
}

Share this post


Link to post
Share on other sites
You could also use an interface instead of a function pointer (if you're scared of function pointers like me).

// the interface (say in your case)
template <typename ElementType>
class IElementProcessor {
public:
virtual ~IElementProcessor() {
}

// does something to passed element
virtual void process( ElementType element ) = 0;
};





So your class 'X' needs to implement this interface:

template <typename ElementType>
class X : public virtual IElementProcessor<ElementType> { // you could use a concrete type here instead of a template if the processor processes only that certain type
public:
X() {};

virtual ~X() {};

// mandatory override from IElementProcessor
void process( ElementType element ) {
// your processing code here
}
}





Then your Map::forEachElement() would be:

// you can use a reference instead of a pointer for the parameter, I just use pointers in my projects
void forEachElement( IElementProcessor* const elementProcessor ) {
for ( Map::iterator it = begin(); it != end(); it++ ) {
elementProcessor->process( it->second );
}
}



Share this post


Link to post
Share on other sites
mazelle - with your approach I get a : error C2664: 'C3DHashtable<T>::forEachElement' : cannot convert parameter 1 from 'void (ElementType)' to 'IElementProcessor<ElementType> *const '
with
[
T=GridCell *
]
and
[
ElementType=GridCell
]
and
[
ElementType=GridCell *
]

I can see that there is the diff between the parameter, but no idea on how it should be changed.

also needed to add void process(T element) implementation to the C3DHashtable class to stop it complaining about abstract classes.



#include "stdafx.h"
#include <string>
#include <iostream>
#include <map>
#include <algorithm>
#include <vector>

using namespace std;

struct GridCell
{
// Alternatice constructor for basic setting up of a cell for path finding
GridCell( double X, double Y, long Z)
{
m_X = X;
m_Y = Y;
m_Z = Z;
}

double m_X;
double m_Y;
double m_Z;
};

static const C_numberOfDimensions = 3;

class C3DHashtableKey
{
public:

C3DHashtableKey(const int iX, const int iY, const int iZ)
{
m_iCoord[0] = iX;
m_iCoord[1] = iY;
m_iCoord[2] = iZ;
}

bool operator<(const C3DHashtableKey & rhs) const
{
return std::lexicographical_compare(m_iCoord, m_iCoord + C_numberOfDimensions, rhs.m_iCoord, rhs.m_iCoord + C_numberOfDimensions);
}

private:

int m_iCoord[C_numberOfDimensions];
};


// the interface (say in your case)
template <typename ElementType>
class IElementProcessor {
public:
virtual ~IElementProcessor() {
}

// does something to passed element
virtual void process( ElementType element ) = 0;
};


template<typename T>
class C3DHashtable : public virtual IElementProcessor<T>
{

private:

std::map<C3DHashtableKey, T> m_map;

public:

C3DHashtable(void)
{
}

~C3DHashtable(void)
{
}

void process(T element)
{

}

void Insert(const int x, const int y, const int z, T value)
{
m_map.insert(std::make_pair(C3DHashtableKey(x, y, z), value));
}

void forEachElement( IElementProcessor<T>* const elementProcessor)
{
for (std::map<C3DHashtableKey, T>::iterator it = m_map.begin(); it != m_map.end(); it++)
{
elementProcessor->process( it->second );
}
}
};


template <typename ElementType>
class X : public virtual IElementProcessor<ElementType> {

public:
X()
{
Map2.Insert(1,2,3, new GridCell(1,2,3));
Map2.Insert(4,5,6, new GridCell(4,5,6));
Map2.Insert(7,8,9, new GridCell(7,8,9));
}

void process(ElementType element )
{

}

void demoTheCallback()
{
Map2.forEachElement(process);
}

private:
C3DHashtable<GridCell*> Map2;
};

int main() {
X<GridCell> x;
x.demoTheCallback();
}



Share this post


Link to post
Share on other sites
Make X a non-template class and derive from IElementProcessor<GridCell *> if you want it to visit a GridCell * container.

Share this post


Link to post
Share on other sites
Just use function objects. They are part of TR1, and of Boost. They are the way to go for callbacks. Also look at bind (boost.bind, tr1 bind ..). A function object is just a container for a function pointer, or a reference to a functor, a method pointer ... a generic way of holding functions as first-class entities. They also have the advantage of working with STL algorithms.

Share this post


Link to post
Share on other sites
Like this?

winds up with comp error - error C2664: 'C3DHashtable<T>::forEachElement' : cannot convert parameter 1 from 'void (GridCell *)' to 'IElementProcessor<ElementType> *const '
with
[
T=GridCell *
]
and
[
ElementType=GridCell *
]



class X : public IElementProcessor<GridCell* > {

public:
X()
{
Map2.Insert(1,2,3, new GridCell(1,2,3));
Map2.Insert(4,5,6, new GridCell(4,5,6));
Map2.Insert(7,8,9, new GridCell(7,8,9));
}

void process(GridCell* lGridCell )
{

}

void demoTheCallback()
{
Map2.forEachElement(process);
}

private:
C3DHashtable<GridCell*> Map2;
};

Share this post


Link to post
Share on other sites
Quote:
Original post by markgame66
mazelle - with your approach I get a : error C2664: 'C3DHashtable<T>::forEachElement' : cannot convert parameter 1 from 'void (ElementType)' to 'IElementProcessor<ElementType> *const '
with
[
T=GridCell *
]
and
[
ElementType=GridCell
]
and
[
ElementType=GridCell *
]

I can see that there is the diff between the parameter, but no idea on how it should be changed.

also needed to add void process(T element) implementation to the C3DHashtable class to stop it complaining about abstract classes.


*** Source Snippet Removed ***


Sorry, it should be:

// you can use a reference instead of a pointer for the parameter, I just use pointers in my projects
void forEachElement( IElementProcessor<T>* const elementProcessor ) {
for ( Map::iterator it = begin(); it != end(); it++ ) {
elementProcessor->process( it->second );
}
}



With the "<T>".

Share this post


Link to post
Share on other sites
Mark, it should be:

class X : public IElementProcessor<GridCell* > {

public:
X()
{
Map2.Insert(1,2,3, new GridCell(1,2,3));
Map2.Insert(4,5,6, new GridCell(4,5,6));
Map2.Insert(7,8,9, new GridCell(7,8,9));
}

void process(GridCell* lGridCell )
{

}

void demoTheCallback()
{
// Map2.forEachElement(process);
Map2.forEachElement( this );
}

private:
C3DHashtable<GridCell*> Map2;
};



A better approach to test it is:

class X : public virtual IElementProcessor<GridCell*> {
public:
void process(GridCell* lGridCell) {
// do X processing here
}
};

// another element processor
class Y : public virtual IElementProcessor<GridCell*> {
public:
void process(GridCell* lGridCell) {
// do Y processing here
}
};


// test main entry
void main() {
C3DHashtable<GridCell*> Map2;
Map2.Insert(1,2,3, new GridCell(1,2,3));
Map2.Insert(4,5,6, new GridCell(4,5,6));
Map2.Insert(7,8,9, new GridCell(7,8,9));

// test X
X xProcessor;
Map2.forEachElement( &xProcessor );

// test Y
Y yProcessor;
Map2.forEachElement( &yProcessor );
}



Try printing stuff in your process() first before coding the actual functionality.

Share this post


Link to post
Share on other sites
Quote:
Original post by markgame66
mattd, I've had a go with your example in my app, getting a compilation error:

error C2784: 'void C3DHashtable<T>::forEachElement(C &,void (__thiscall C::* )(const T & ))' : could not deduce template argument for 'void (__thiscall T1::* )(const T & )' from 'void (__thiscall X::* )(const GridCell &)'
with
[
T=GridCell *
]
and
[
T=GridCell *
]


Can you see where I've got this wrong. An abridged version of my code below (I'm not 100% about the functionality, but is does at least exibit the same compilation problems.

Thanks

Mark


*** Source Snippet Removed ***


You need to use void callback(const GridCell *& t) instead. Remember, you have T = GridCell *, as pointed out by the error.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this