namespace sid
namespace sid

Reputation: 1936

C++ design pattern for operations on subclasses in an inherted class

I have an abstract base class that enforces certain operations on all derived classes. In addition to this, I would like to enforce certain other operations that are specific to subclasses declared in the derived classes.

The following is a minimal example:

class Base {
    public:
        virtual void init() = 0;
        virtual void reset() = 0;
};

class Derived1 : public Base {
    class Data {
        int *x1;
    public:
        Data() {
            x1 = NULL;
        }

        void alloc(int num) {
            x1 = new int[num];
        }

        ~Data() {
            delete[] x1;
            x1 = NULL;
        }
    } data;

public:
    void init() { ... }
    void reset() { ... }

    void resetData() { 
        data.~Data(); 
    }
};

class Derived2 : public Base {
    class Data {
        float *x2;
    public:
        Data() {
            x2 = NULL;
        }

        void alloc(int num) {
            x2 = new float[num];
        }

        ~Data() {
            delete[] x2;
            x2 = NULL;
        }
    } data;

public:
    void init() { ... }
    void reset() { ... }

    void resetData() { 
        data.~Data(); 
    }
};

In the example above Base enforces the init() and reset() methods on all derived classes.

In addition to this I would like to enforce that all derived classed have

  1. a member variable of named data and
  2. a method called resetData() that calls the destructor on this variable
  3. a method called Data &getData() that gets a reference to the variable

What would be the best way to achieve this?

Upvotes: 0

Views: 213

Answers (3)

namespace sid
namespace sid

Reputation: 1936

Thanks for your responses. This is what I have come up with. I know I am breaking my original requirements i.e. Data must be a subclass of the derived classes, but the following design pattern ensures that all derived classes have a data member variable, each of a different type, but again, each of these have some basic methods enforced on them. I think this is what @emsr suggested in one of his comments.

Also now resetData() is done in a separate method. Thanks to @LuchianGrigore for pointing out a possible problem with explicitly calling the destructor. Now this method explicitly instead. The virtual destructor will also call the same function. I know that I shouldn't call virtual functions from a destructor but by setting the scope of the function explicitly, I hope I am avoiding any ambiguity here. (Or is this also a problem?)

struct Data {
    virtual void resetData() = 0;
    virtual ~Data() {}
};

template<typename _DT>
class Base {
protected:
    _DT data;

public:
    _DT &getData() {
        return data;
    }

    void resetData() {
        data.resetData();
    }

    virtual void init() = 0;
    virtual void reset() = 0;
};

struct Data1 : public Data {
    int *x;

    Data1() {
        x = NULL;
    }

    void alloc(int num) {
        x = new int[num];
    }

    virtual void resetData() {
        delete[] x;
        x = NULL;
    }

    virtual ~Data1() {
        Data1::resetData();
    }

};

class Derived : public Base<Data1> {
public:
    virtual void init() {
        // Carry out other init operations
        Data1 &x = getData();
        x.alloc(10);
    }

    virtual void reset() {
        // Carry out other reset operations
        data.resetData();
    }
};

Upvotes: 0

user258808
user258808

Reputation: 734

I would template the base class on the Data type and move the Data definition out of the derived class. The downside is that you do not have one single type for the Base anymore.

template <class Data>
class Base {
public:
  virtual ~Base() {}
  virtual void init() = 0;
  virtual void reset() = 0;
  virtual Data& getData() {
    return data;
  }
  virtual void resetData() {
    data.reset();
  }

protected:
  Data data;
};

class Data1 {
  int *x1;
public:
  Data1() {
    x1 = 0;
  }

  void alloc(int num) {
    x1 = new int[num];
  }

  void reset() {
    delete[] x1;
    x1 = 0;
  }

  ~Data1() {
    delete[] x1;
    x1 = 0;
  }
};



class Derived1 : public Base<Data1> {
public:

public:
  void init() { }
  void reset() { }
};


class Data2 {
  float *x2;
public:
  Data2() {
    x2 = 0;
  }

  void reset() {
    delete[] x2;
    x2 = 0;
  }

  void alloc(int num) {
    x2 = new float[num];
  }

  ~Data2() {
    delete[] x2;
    x2 = 0;
  }
};


class Derived2 : public Base<Data2> {
public:
public:
  void init() { }
  void reset() { }
  Data2& getData() {
    return data;
  }
  void resetData() {
    data.reset();
  }
};

Another way to do this would be to inherit the Data class from a single base class. In this case you won't be able to force the name of the member variable to be data though.

class IData {
public:
  virtual ~IData() {}
  virtual void reset() = 0;
};

class Base {
public:
  virtual ~Base() {}
  virtual void init() = 0;
  virtual void reset() = 0;
  virtual IData& getData() = 0;
  virtual void resetData() = 0;
};

class Derived1 : public Base {
  class Data : public IData {
    int *x1;
  public:
    Data() {
      x1 = 0;
    }

    void alloc(int num) {
      x1 = new int[num];
    }

    void reset() {
      delete[] x1;
      x1 = 0;
    }

    ~Data() {
      delete[] x1;
      x1 = 0;
    }
  } data;

public:
  void init() { }
  void reset() { }

  IData& getData() {
    return data;
  }
  void resetData() {
    data.reset();
  }
};

class Derived2 : public Base {
  class Data : public IData {
    float *x2;
  public:
    Data() {
      x2 = 0;
    }

    void reset() {
      delete[] x2;
      x2 = 0;
    }

    void alloc(int num) {
      x2 = new float[num];
    }

    ~Data() {
      delete[] x2;
      x2 = 0;
    }
  } data;

public:
  void init() { }
  void reset() { }
  IData& getData() {
    return data;
  }
  void resetData() {
    data.reset();
  }
};

Upvotes: 0

Luchian Grigore
Luchian Grigore

Reputation: 258618

  • a member variable of named data and
  • a method called resetData() that calls the destructor on this variable
  • a method called Data &getData() that gets a reference to the variable

Seems to me like you need these in your base class, if they're common for all deriving classes.

class Base {
    public:
        Data data;
        void resetData();  //if data is not a pointer, are you sure you want 
                           //to call its destructor?
                           //this will lead to undefined behavior when
                           //Base is destroyed, as data will automatically 
                           //be freed
        Data& getData();
        virtual void init() = 0;
        virtual void reset() = 0;
};

Your class still remains abstract, just in case you though this was an issue.

Without this approach:

  • there's no way to enforce a deriving class declaring a member
  • you could look into the template method pattern, but again, I don't see the point
  • you could have a pure virtual getData, but again, I don't see the point

From a design point of view, you should have all these in your base class.

Upvotes: 2

Related Questions