Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access derived class member variable in virtual function

class Car {
    class BaseState {
       explicit BaseState(Car* vehicle) : mVehicle(vehicle) {}
       virtual void run() = 0;

       Car* mVehicle;
    }
    class State1 : public BaseState {
       explicit State1(Car* vehicle) : BaseState(vehicle) {}
       virtual void run() {
           // use data of Car
           ...
           doSomething();
       }
       virtual void doSomething() {
       }
    }
    class State2 : public BaseState {
    }
    ...
}

class Convertible: public Car {
    class State1 : public Car::State1 {
       explicit State1(Convertible* vehicle) : Car::State1(vehicle) {}
       virtual void doSomething() {
           static_cast<Convertible*>(mVehicle)->foldTop();
       }
    }
    class State2 : public Car::State2 {
    }
    ...
    void foldTop() {}
}

All States are derived from the BaseState so that they have the member variable mVehicle to access outer class variables. However, in each derived class, in all functions of each State, static_cast is needed to access derived class member variables and functions.

Any better solution?

  1. In each State of derived classes, add another pointer (e.g., Convertible *mConvertible). Each State has duplicate pointers (mConvertible and mVehicle) point to the same object. Does not look right.
  2. Use a virtual Getter instead of mVehicle in base class. There will be excessive Getter calls in base class.

=======================================================================

Yes. I tried template as below, but it cannot be compiled because errors like

"car.h: In member function ‘virtual void Car::State1::run()’: car.h:18:12: error: ‘mVehicle’ was not declared in this scope ".

// car.h
#include <iostream>

template <class T>
class Car {
public:
    class BaseState {
    public:
       explicit BaseState(T* vehicle) : mVehicle(vehicle) {}

    protected:
       T* mVehicle;
    };

    class State1 : public BaseState {
    public:
       explicit State1(T* vehicle) : BaseState(vehicle) {}
       virtual void run() {
           mVehicle->x = 1;
           mVehicle->y = 2;
           mVehicle->doSomething1();
           mVehicle->doSomething2();
           processEvent();
       }
       virtual void processEvent() {
           if (mVehicle->val > 2) {
                std::cout << "too large" << std::endl;
           }
       }
    };

    class State2 : public BaseState {
    public:
       explicit State2(T* vehicle) : BaseState(vehicle) {}
       virtual void run() {
           mVehicle->x = 10;
           mVehicle->y = 20;
           processEvent();
       }
       virtual void processEvent() {
           if (mVehicle->val > 20) {
                std::cout << "too large" << std::endl;
           }
       }
    };

    virtual void doSomething1() {
        val += x * y;
    }

    virtual void doSomething2() {
        val += x + y;
    }

protected:
    int x;
    int y;
    int val;

};

// convertible.h
#include "car.h"
#include <iostream>

class Convertible : public Car<Convertible> {
protected:
    class State1 : public Car<Convertible>::State1 {
       explicit State1(Convertible* vehicle) : Car<Convertible>::State1(vehicle) {}
       // want to override functions in base class states
       virtual void processEvent() {
           if (mVehicle->val > 10) {
                std::cout << "too large" << std::endl;
                mVehicle->val = 10;
           }
       }
    };

    // want to override some base class functions
    // and access some special variables
    // want to inherit other functions
    virtual void doSomething2() {
        z = 10;
        val += x + y + z;
    }

protected:
    int z;
};

If I use State1(Car* vehicle) instead of State1(T* vehicle), there is additional conversion error. What am I doing wrong?

If the program can figure out that Convertible::State1::processEvent() should be executed, why cannot it automatically cast mVehicle from Car* to Convertible*? Apparently mVehicle points to a Convertible object when Convertible::State1::processEvent() is deduced. We do not need template if there is automatic cast.

like image 851
dekst Avatar asked Oct 08 '15 08:10

dekst


2 Answers

Use templates.

Remove pointer from Car inner classes (made them abstract classes full of pure virtuals).

Add new template class CarT (or think about better name)

template <typename T>
class CarT {

class CarHolder {
   explicit CarHolder(T* car) : car(car) {}
   T* car;
};
class State1 : public Car::State1, protected CarHolder {
   explicit State1(Car* vehicle) : CarHolder(vehicle) {}
   virtual void run() {
       // use data of Car
       ...
       doSomething();
   }
   virtual void doSomething() {
   }
};
class State2 : public Car::State2 {
};
...
};

This way you will have runtime polymorphism of Car and it's State's and good compile time polymorphism of derived classes (which in turn will remove need for ugly static_cast)

class Convertible: public CarT<Convertible> {
    typename CarT<Convertible> Base;
    class State1 : public Base::State1 {
       explicit State1(Convertible* vehicle) : Car::State1(vehicle) {}
       virtual void doSomething() {
           car->foldTop();
       }
    }
    class State2 : public Base::State2 {
    }
    ...
    void foldTop() {}
}

class Convertible : public CarT<Convertible> might look strange, but it will work (CarT uses it template argument only as pointer, if it was using it as value member there might be some problems)

like image 167
Hcorg Avatar answered Oct 27 '22 13:10

Hcorg


This implementation uses no casts, duplicate pointers, virtual getters, or CRTP. It has three parallel hierarchies:

  • cars
  • abstract car states which are pure abstract interfaces
  • concrete car states, where the state is parameterized by the actual run-type type of the car.

So we have e.g.

Car                   Car::AbstractState                Car::State<C>
|                     |                                 |
+--- Convertible      +--- Convertible::AbstractState   +--- Convertible::State<C>
|    |                |    |                            |    |
|    +--- Racer       |    +--- Racer::AbstractState    |    +--- Racer::State<C>
+--- Hybrid           +--- Hybrid::AbstractState        +--- Hybrid::State<C>

Each concrete state derives from and implements the corresponding abstract state. If we have a Car* that points to a Convertible, and we query its state, we get a Car::AbstractState* which points to a concrete state object with the ultimate type of Convertible::State<Convertible>. The user of the car hierarchy, however, doesn't know and doesn't care about the template machinery.

The code:

#include <iostream>
using namespace std;

struct Trace
{
    Trace(const char* s) : s (s)
    {
        cout << s << " start\n";
    }

    ~Trace()
    {
        cout << s << " end\n";
    }

    const char* s;
};

struct Car {
    struct AbstractState
    {
        virtual void run() = 0;
    };

    template <typename C>
    struct State : virtual AbstractState
    {
        explicit State(C* vehicle) : mVehicle(vehicle) {}
        virtual void run()
        {
            Trace("Car::State::run");
            doSomething();
        };
        virtual void doSomething()
        {
            Trace("Car::State::doSomething");
        }
        C* mVehicle;
    };

    virtual AbstractState* getState() { return new State<Car>(this); }
};


struct Convertible : Car {

    struct AbstractState : virtual Car::AbstractState
    {
        virtual void runBetter() = 0;
    };

    template <typename C>
    struct State : Car::State<C>, virtual AbstractState
    {
        using Car::State<C>::mVehicle;
        explicit State(C* vehicle) : Car::State<C>(vehicle) {}
        void doSomething()
        {
            Trace("Convertible::State::doSomething");
            Car::State<C>::doSomething();
            mVehicle->foldTop();
        }

        void runBetter()
        {
            Trace("Convertible::State::runBetter");
            run();
            doSomethingElse();
        };

        virtual void doSomethingElse()
        {
            Trace("Convertible::State::doSomethingElse");
        }
    };

    void foldTop()
    {
        Trace("Convertible::foldTop");
    }

    Convertible::AbstractState* getState() { return new State<Convertible>(this); }
};

int main ()
{
    Car car;
    Convertible convertible;
    Car& car2(convertible);

    cout << "runing car\n";
    Car::AbstractState* carstate = car.getState();
    carstate->run();

    cout << "runing convertible\n";
    Convertible::AbstractState* convertiblestate = convertible.getState();
    convertiblestate->run();

    cout << "runing car2\n";
    Car::AbstractState* carstate2 = car2.getState();
    carstate2->run();
}
like image 35
n. 1.8e9-where's-my-share m. Avatar answered Oct 27 '22 12:10

n. 1.8e9-where's-my-share m.