Doubting typedefs inside a class in C ++

I need to define a function that will return a class object.

Suppose I have a class called dog (class declaration in .h and implementation in .cpp), and when the dog dies, I want to call a function to tell the dog manager. In SetFunc in dog, I get a function that will be called:

Dog.h

class Dog
{
public:

    typedef void (*OndogDie)(Dog * dog);

    OndogDie function;

    void SetFunc(OndogDie func);
    void Die();
};

      

Dog.cpp

void Dog::SetFunc(Dog::OndogDie func)
{
    function = func;
}

void Dog::Die()
{
    function(this);
}

      

If I put the typedef inside the Dog class everything works, exclude the Manager class, which cannot pass a suitable callback for the dog class as a parameter:

Manager.h

#include "Dog.h"

class Manager
{
public:
    Manager();
    void CbdogDie(Dog * dog);
};

      

Manager.cpp

void Manager::CbdogDie(Dog * d)
{
    //stuff
}

Manager::Manager()
{
    Dog d = Dog();
    d.SetFunc(CbdogDie); (Error: argument of type void(Manager::*)(Dog *d) is incompatible with parameter of type "Dog::OndogDie")
}

      

I have looked in many tutorials and answered questions already, but none of them helped me figure out what is wrong with my code. I also looked into using the delegate type, but it only worked in the clr project.

I think the problem might be where I put the typedef function. Is there another place to define this function? If not, then there is a solution to this problem, or at least an alternative to it.

thank

+3


source to share


3 answers


Repeating my comment for the convenience of the readers: CbdogDie

(in your code) is a member of a function (non-static) and requires an instance call Manager

, so its type is incompatible with your callback typedef

.

Not related to your mistake, but still worth considering: Shouldn't you move the logic that invokes the callback into the destructor Dog

? In C ++ (unlike Java, for example) we know when our objects are destroyed.

Not sure if you can apply this to your actual problem, but here are some examples that work.

#include <iostream>
#include <string>

class Dog
{
public:

  typedef void (* on_dog_die_callback_t)(const Dog&);

  Dog(const std::string& name, on_dog_die_callback_t callback) :
    name_(name), callback_(callback)
  {
    std::clog << "A new dog named " << this->getName() << " is born.\n";
  }

  ~Dog()
  {
    std::clog << "A dog named " <<  this->getName() << " has died.\n";
    if (this->callback_)  // allow NULL pointer for no-op
      this->callback_(*this);
  }

  std::string
  getName() const
  {
    return this->name_;
  }

private:

  std::string name_;
  on_dog_die_callback_t callback_;
};

// Note: This is a free function, not a class member.
void
friendly_callback(const Dog& dog)
{
  std::clog << "We are so sorry that " << dog.getName() << " died.\n";
}

struct Manager
{
  // Note: This is a static function.
  static void
  unfriendly_callback(const Dog& dog)
  {
    std::clog << "Haha, " << dog.getName() << " died.\n";
  }
};

int
main()
{
  Dog fido("Fido", friendly_callback);
  Dog leika("Leika", Manager::unfriendly_callback);
  Dog waldo("Waldo", 0);
}

      

The result of running this code:



A new dog named Fido is born.
A new dog named Leika is born.
A new dog named Waldo is born.
A dog named Waldo has died.
A dog named Leika has died.
Haha, Leika died.
A dog named Fido has died.
We are so sorry that Fido died.

      

Note that I (unnecessarily) changed the signature of your callback to accept a reference const

to Dog

, not a pointer. This is generally considered to be the best style, but if you need a pointer for some reason I can't see, you can do that as well.

Note: If you choose to call the callback from your destructor, keep in mind that there are some unusual things to observe:

  • Calling the callback early in your destructor. If your class Dog

    owns resources that are freed in destuructor before the callback is called, you pass in a pointer this

    pointing to the partially destroyed object and calling any member function on it will most likely cause undefined behavior.
  • The callback should not call the functions of the function virtual

    members Dog

    it passed. Note that the destructor Dog

    will only be called after the destructor of the derived class (if any), so it can deal with an already partially destroyed object. As long as the callback is only interested in the base Dog

    , that's fine, but if it tries to access any members of the derived class (via function calls virtual

    ) it will result in undefined behavior.

The good news: if your class is simple (no virtual

functions, no explicit resources released in the destructor), you should be fine.

+2


source


You have defined function

a function pointer (non member). You can easily check this if you create a dummy function outside of any class:

     void gondie(Dog * dog) {}

      

and if you change your Manager()

to:

     d.SetFunc(gondie);  // compile 

      

But it Manager::CbdogDie()

is a member function of the Manager class. To call it, you need to know which manager to use, which Dog: Die () doesn't: there you only know the function, not the object.

Edit: Additional suggestions:

You already have a very good solution for the callback approach, but for entries, I offer you another option with a slight design change and a dog reference to your manager.



Since the manager is not yet known in the Dog header, we will use an adapter class DogManager

whose sole purpose is to offer a dying callback function:

class Dog
{
public: 
    class DogManager {       // adapter 
    public:
        void CbdogDie(Dog *d) {}    // empty function to call back.  Will be overloaded later
    };

private: 
    DogManager *mymanager;    // Who is the master of the dog ?  

public:
...  // remaining of your dog definition
void SetManager(DogManager *m);
};

      

The implementation of the dog has changed a bit:

void Dog::Die()
{
    mymanager->CbdogDie(this);  // call directly manager function instead of callback
}

      

The last thing to do is to make your class Manager

inherit from DogManager

:

class Manager : Dog::DogManager
...

      

and of course change its implementation to inform the dog, replacing the old one d.SetFunc(...)

with d.SetManager(this)

;

+1


source


You can't go wrong. Each dog must keep a reference to the manager and must report death to the manager in the dog's destructor. The problem occurs when the manager itself is destroyed and some dogs are still there, so the link becomes invalid. This can be solved using a static manager instance or a singleletone (a unique DogManager instance automatically created when the first dog is created).

0


source







All Articles