3

I am creating a class which is callback based. I need to give the client a freedom to invoke the callback that he defines. For example, I have a class Base:

class Base
{
...
 public:
    virtual void OnMsgReceived(const char *msg, char *&response);
    virtual void OnMsgReceived(const char *msg, string &response);
};

The client MUST implement either one, but not 2. How do I know which one he has implemented so that when I handle the callback, I call the right one?

I know I can't do it during the construction instantiation of the object, but once the object is instantiated, is there any way for me check which one of these virtual functions has been implemented? Thanks.

Aesthete
  • 18,622
  • 6
  • 36
  • 45
Leonid
  • 31
  • 1
  • 1
    Why can't client implement both the `virtual` functions? – iammilind Nov 10 '12 at 04:09
  • 2
    Just my 2 cents, I would avoid using this design and providing what appears to be two callbacks for a single event, i.e. OnMsgReceived, provided the same condition leads to the callback being executed. If they are truly different, then they should be named differently. – Matthew Nov 10 '12 at 04:10

5 Answers5

0

In C++ without pure virtual technique, there is no way to know at runtime if a virtual function is implemented or not by a class.

Define both the functions as pure virtual functions and let the client implement both of them. You still can have body of pure virtual function for Base if needed.

The client MUST implement either one, but not 2.

Assuming that both versions are genuinely needed, I don't feel a need of having the above requirement.

How do I know which one he has implemented so that when I handle the callback, I call the right one?

Once you assign a proper object to a Base reference/pointer, you don't have to worry about this. Compiler does that dirty job for you.

On side note, you can also explore CRTP technique. Where a derived class must implement a given method to pass the compilation. However that won't be as flexible as the virtual functionality.

iammilind
  • 68,093
  • 33
  • 169
  • 336
0

I would like to add something to the iammilind's response:

If you use a pure virtual function in a class you are making that class abstract and, as consequence, you can't instanciate it.

if A is an abstract class

class A
{
  public:
  //
  virtual void foo() = 0;
  //
}

you can't write

A myA

One good resource about this is, as usual, the Parashift C++ FAQ website.

Community
  • 1
  • 1
user1797612
  • 812
  • 6
  • 22
0

Absurd approach: dispatch in the base class from one to the other. Unless the user has overridden at least one of them that will lead to a stackoverflow that is easily detectable:

void Base::OnMsgCallback(const char *msg, char*& response) {
   std::string resp;
   OnMsgCallback(msg,resp);
   response = new char[resp.size()+1]();
   std::copy(resp.begin(), resp.end(), response);
}
void Base::OnMsgCallback(const char *msg, std::string& response) {
   char *resp;
   OnMsgCallback(msg,resp);
   response = resp;
   delete [] resp;
}

But this is a terrible hack, and as you might have noticed the char* version requires manual handling of resources, which is error prone. You are much better off by defining a single interface that passes a reference to a std::string (or has a return type of std::string, which simplifies the contract (is response an inout or just out parameter?) and the code (no need to create the std::string before calling the callback.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • It is actually quite clever, but not a solution: by the time you detect the error, it is already too late, stackoverflow! and if user override both function, it is out of control either. – Baiyan Huang Nov 10 '12 at 05:05
0

It is not easy to figure out which function has been overridden - even if you can, it is also a design which is very hard for developers to follow: The client MUST implement either one, but not 2 - developers may forget it, and they may not like it!

I would suggest an alternative way based on you design:

Your framework

class Base0
{
public:
    // other basic members

};

template<class T>
class Base : public Base0
{
    virtual void OnMsgReceived(const char *msg, T &response) = 0;
};


typedef Base<char*> BaseChar;

typedef Base<string&> BaseString;



void run(Base0* pBase)
{
    const char* msg = getmsg();
    BaseChar* p = dynamic_cast<BaseChar*>(pBase);
    if(p != NULL)
    {
        char* response; // 
        p->OnMsgReceived(msg, response)
        return;
    }

    BaseString* p2 = dynamic_cast<BaseString*>(pBase);
    if(p2 != NULL)
    {
        string response; // 
        p2->OnMsgReceived(msg, response)
        return;
    }

}

Client Code

class Derived: public BaseChar
{
public:
    virtual void OnMsgReceived(const char *msg, char* &response)
    {
        // do sth
    }       
};

User of your framework needs to implement either BaseChar, or BaseString, then everything just works.

Baiyan Huang
  • 6,463
  • 8
  • 45
  • 72
  • I think there's a typo in `template class Base : public Base`, shouldn't it be `template class Base : public Base0`? – kcm1700 Nov 10 '12 at 07:16
0

That design is bad for so many reasons, anyway there is a way to do your way:

class Base
{
public:
    virtual void OnMsgReceived(const char *msg, char *&response)
    {
        response = (char*)"OMG! nothing derived"; // or throw if you want
    }

    virtual void OnMsgReceived(const char *msg, string &response)
    {
        char *ptr_to_dest = 0;
        OnMsgReceived(msg, ptr_to_dest);
        response = ptr_to_dest;
    }
};

You always call the one using string, you don't need to check which one they did.

This is what will happen depending on what they implement:

  • Only implement char version -> it will be called and converted to string
  • Only implement string version -> it will be called directly
  • Implement both versions -> only string will be called
  • implement nothing -> you will get your default message (or a exception if you throw)
aaronps
  • 324
  • 1
  • 6