foboi1122
foboi1122

Reputation: 1757

Mapped functors to member functions losing scope

I'd like to be able to call some member functions from different classes which all have the same general syntax and base class. Something along the lines of

class A: public BaseClass
{
public:
    A();
    ~A();

    int DoFoo();
    int DoBar();
    int DoBarBar();
};

class B : public BaseClass
{
public:
    B();
    ~B();

    int DoSomething();
    int DoSomethingElse();
    int DoAnother();
};

Where I could potentially places the member functions from both classes into one map so that I could have something like

 key           value
"Option1"     *DoFoo()
"Option2"     *DoSomething()
"Option3"     *DoFoo()
 ...            ...
"Option6"     *DoAnother()

Where I could call a function to return a value based on what option I chose, regardless of what class the function belongs to.

Through some searching, I tried to implement my own mapped set of functors. However, the map retains the address of the functor, but the functions within become null.

Here are my functor declarations which store a class object and a function pointer

    #include <stdio.h>
    #include <vector>
    #include <algorithm>
    #include <map>
    #include <string>

//////////////////////////////////////////////////////////////
//Functor Classes
////////////////////////////////////////////////////////////// 
    class TFunctor
    {
    public:
      virtual void operator()()=0;  // call using operator
      virtual int Call()=0;        // call using function
    };


    // derived template class
    template <class TClass> class TSpecificFunctor : public TFunctor
    {
    private:
      int (TClass::*fpt)();   // pointer to member function
      TClass* pt2Object;                  // pointer to object

    public:

      // constructor - takes pointer to an object and pointer to a member and stores
      // them in two private variables
      TSpecificFunctor(TClass* _pt2Object, int(TClass::*_fpt)())
         { pt2Object = _pt2Object;  fpt=_fpt; };

      // override operator "()"
      virtual void operator()()
       { (*pt2Object.*fpt)();};              // execute member function

      // override function "Call"
      virtual int Call()
        {return (*pt2Object.*fpt)();};             // execute member function
    };

    typedef std::map<std::string, TFunctor*> TestMap;

//////////////////////////////////////////////////////////////
//Test Classes
//////////////////////////////////////////////////////////////
//Base Test class 
class base
{
public:
    base(int length, int width){m_length = length; m_width = width;}
    virtual ~base(){}


    int area(){return m_length*m_width;}

    int m_length;
    int m_width;
};

//Inherited class which contains two functions I would like to point to
class inherit:public base
{
public:
    inherit(int length, int width, int height);
    ~inherit();
    int volume(){return base::area()*m_height;}
    int area2(){return m_width*m_height;}

    int m_height;
    TestMap m_map;
};

where my inherit class constructor looks like:

inherit::inherit(int length, int width, int height):base(length, width)
{
    m_height = height;
    TSpecificFunctor<inherit> funcA(this, &inherit::volume);
    m_map["a"] = &funcA;

    TSpecificFunctor<inherit> funcB(this, &inherit::area2);
    m_map["b"] = &funcB;
}

Which is where I am mapping two functions into a map. Things still look okay in the above function in terms of memory address and function pointers.

I then try to create an instance of inherit in a new class...

class overall
{
public:
    overall();
    ~overall(){}

    inherit *m_inherit;
    TestMap m_mapOverall;
};
overall::overall()
{
    m_inherit = new inherit(3,4,5);

    TestMap tempMap = m_inherit->m_map;
    int i = 0;
}

Here when I look at the values of m_inherit->m_map, I notice that the keys are still consistent, however the memory addresses of the functions which I tried to point to have disappeared.

I haven't had much experience with functors but from my understanding, it is able to retain states, which I assume means that I can call member functions outside of its class. But I'm starting to think that my member functions disappear because it is out of scope.

Upvotes: 2

Views: 684

Answers (2)

Tyler Hyndman
Tyler Hyndman

Reputation: 1400

You are right, it is a scooping issue. In the inherit constructor, funcA and funcB are both allocated on the stack and destroyed once the function goes out of scope. The leaves m_map with stale pointers.

What you really want is something like

inherit::inherit(int lenght, int width, int height) :base(length, width)
{
    m_height = height;

    // allocated on the heap
    TSpecificFunctor<inherit> * funcA = new TSpecificFunctor<inherit>(this, &inherit::volume);
    m_map["a"] = funcA;

    // allocated on the heap
    TSpecificFunctor<inherit> * funcB = new TSpecificFunctor<inherit>(this, &inherit::area2);
    m_map["b"] = funcB;
} // when this function exits funcA and funcB are not destroyed

But, to avoid any memory leaks, the destructor for inherit will need to clean up the values

inherit::~inherit()
{
    for(TestMap::iterator it = m_map.begin(); it != m_map.end(); ++it) {
        delete it->second;
    }
}

Using new and delete can easily lead to memory leaks. To prevent them, I would suggest looking into smart points like std::unique_ptr and std::shared_ptr. Also, functors are becoming obsolete with the introduction of lambdas in C++11. They are really neat and worth looking into if you are not familiar with them.


If your compiler supports them, to do this with lambdas

#include <functional>

// ...

typedef std::map<std::string, std::function<int(void)>> TestMap;

// ...

inherit::inherit(int length, int width, int height):base(length, width)
{
    m_height = height;
    m_map["a"] = [this]() -> int { return volume(); };
    m_map["b"] = [this]() -> int { return area2(); };

    // can be called like so
    m_map["a"]();
    m_map["b"]();
}

// No need to clean up in destructors

Upvotes: 2

Fraser
Fraser

Reputation: 78280

You're right - the TSpecificFunctors go out of scope at the end of inherit's constructor, so you shouldn't keep pointers to them.

If you can, prefer smart pointers, e.g.

#include <memory>

...

typedef std::map<std::string, std::shared_ptr<TFunctor>> TestMap;

...

inherit::inherit(int length, int width, int height):base(length, width)
{
    m_height = height;
    m_map["a"] = std::shared_ptr<TSpecificFunctor<inherit>>(
        new TSpecificFunctor<inherit>(this, &inherit::volume));
    m_map["b"] = std::shared_ptr<TSpecificFunctor<inherit>>(
        new TSpecificFunctor<inherit>(this, &inherit::area2));
}

Your main concern then is to ensure that the functors in m_inherit->m_map are not invoked after m_inherit is destroyed or you will get undefined behaviour. In this case, you're safe since you leak m_inherit (it's never destroyed).

Upvotes: 1

Related Questions