Reputation: 1936
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
resetData()
that calls the destructor on this variableData &getData()
that gets a reference to the variableWhat would be the best way to achieve this?
Upvotes: 0
Views: 213
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
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
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:
getData
, but again, I don't see the pointFrom a design point of view, you should have all these in your base class.
Upvotes: 2