Search code examples
c++functorderived-class

How to iterate through list of derived function objects and accessing derived objects member variables


I have a function object definition:

struct BaseFunctor
{
    std::string desc = "Not this one!";
    virtual double operator()(double a, double (*func) (double)) = 0;
};

and a set of derived function object definitions:

struct DerivedFunctor1 : public BaseFunctor
{
    std::string desc = "Yes this!";
    virtual double operator()(double a, double (*func) (double))
    {
        return a * func(a);
    }
};

struct DerivedFunctor2 : public BaseFunctor
{
    std::string desc = "This is also correct!";
    virtual double operator()(double a, double (*func) (double))
    {
        return 5 * a * func(a);
    }
};

They are instantiated and used in the following way:

double f1(double x){
    return x*x+x;
}

template <typename T, typename F>
void do_something(T &func, F &derived)
{
    double a = 1.0;
    double res = derived(a, func);
    std::cout << derived.desc << std::endl;
    std::cout << "Result is: " << res << std::endl;

}

int main()
{
    std::vector<BaseFunctor*> functors;
    DerivedFunctor1 *derived1 = new DerivedFunctor1;
    DerivedFunctor2 *derived2 = new DerivedFunctor2;
    functors.push_back(derived1);
    functors.push_back(derived2);

    for (auto &f : functors)
    {
        do_something(f1, *f);
    }
}

Now, the reason that the two function objects are derived from BaseFunctor was so I could collect them in a standard container and iterate through them. Are there other and more efficient ways to iterating through function objects?

Secondly, running the code outputs

Not this one!
Result is: 2
Not this one!
Result is: 10

When I try to access the member variable desc, I get the member variable of the parent class. I could write getters and get access to the member variables of the derived function objects in that way but that seems like a lot of work if there are many member variables to the function objects. Are there any other way of achieving this?


Solution

  • Your derived classes define another member named desc in addition to existing BaseFunctor::desc.

    What you rather need is to initialize BaseFunctor::desc with the correct string. Example:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    struct BaseFunctor {
        std::string const desc;
        virtual double operator()(double a, double (*func) (double)) = 0;
        virtual ~BaseFunctor() noexcept = default;
    protected:
        BaseFunctor(std::string desc) noexcept
            : desc(move(desc))
        {}
    };
    
    struct DerivedFunctor1 : public BaseFunctor {
        DerivedFunctor1() : BaseFunctor("Yes this!") {}
        double operator()(double a, double (*func) (double)) override { return a * func(a); }
    };
    
    struct DerivedFunctor2 : public BaseFunctor {
        DerivedFunctor2() : BaseFunctor("This is also correct!") {}
        double operator()(double a, double (*func) (double)) override { return 5 * a * func(a); }
    };
    
    template <typename T>
    void do_something(T &func, BaseFunctor &derived) {
        double a = 1.0;
        double res = derived(a, func);
        std::cout << derived.desc << '\n';
        std::cout << "Result is: " << res << '\n';
    }
    
    double f1(double a) noexcept { return a * a + a; }
    
    int main() {
        using P = std::unique_ptr<BaseFunctor>;
        std::vector<P> functors;
        functors.push_back(P(new DerivedFunctor1));
        functors.push_back(P(new DerivedFunctor2));
        for (auto &f : functors)
            do_something(f1, *f);
    }
    

    A few other changes:

    • BaseFunctor must have a virtual destructor if objects of derived classes get deleted through BaseFunctor*, and your code suggests that.
    • Derived classes overridden functions should use override instead of virtual for the compiler to catch errors if you try to override a function that doesn't exists or have different parameters and/or return type. With virtual it introduces a new function overload with the same name, in this case.
    • std::unique_ptr is used to avoid manual cleanup and leaking memory.
    • BaseFunctor::desc made const, so that it must be initialized in the initializer list in BaseFunctor. That also makes BaseFunctor non-copyable and non-movable, which avoids accidental copy of derived class objects with slicing.