Inheriting only one time
Let's suppose we have a class:
class vector {
public:
class itr;
int *array;
int get(int
no);
itr
getitr(int no);
};
It encapsulates some kind of data structure. This is just an example,
so I put only the most important members here, and there is no privacy.
And it has another class as a member:
class vector::itr {
public:
int *pointer;
void next() {
++pointer; }
int
operator*() { return *pointer; }
};
In this example, this is class which allows iterating through objects
stored in data strucure.
And the vector class has a twin brother:
class list {
public:
class itr;
struct node {
int data;
int *next;
};
node *head;
node *tail;
int
getman(int no);
itr
getitr(int no);
};
This is another implementation of data structure. It provides the same
methods as previous class, so that there is no difference whether we work
on any of that classes.
So, it same has a class as a member:
class list::itr {
public:
node
*pointer;
void next() {
pointer=pointer->next; }
int
operator*() { return pointer->data; }
};
Now we create a template class, which gets as an argument one of either
vector or list:
template<class C>
class adder : public C {
public:
int sum();
}
This is an example of use of data structure classes - it iterates through
all data and calculates a sum.
Now we want to create a class that will need the itr class to
provide greater functionality:
template<class C>
class divcalculator : public C {
public:
class itr;
double
getvalue(int);
itr
getitr(int no, double data);
}
For example, to be able to use some additional data:
template<class C>
class divcalculator<C>::itr : public C::itr {
public:
double
*data;
divcalculator<C>::itr(double *p) : data(p) {}
double
getdivvalue() { return operator*() / data[ operator*() ]; }
};
So far, so good. Everything works.
Now, we want to create one more class, similar to the previous, but
providing different services:
template<class C>
class mulcalculator : public C {
public:
class itr;
double
getvalue(int);
itr
getitr(int no, double *data);
}
That alters itr in the same way:
template<class C>
class mulcalculator<C>::itr : public C::itr {
public:
double
*data;
mulcalculator<C>::itr(double *p) : data(p) {}
double
getdivvalue() { return operator*() * data[ operator*() ]; }
};
The problem is what happens if we try just to combine functionality of divcalculator
and mulcalculator?
We can use multiple inheritance: create new class that inherits both
from divcalculator and mulcalculator, but this is not a
good idea. We want to calculate on the same values, not on two copies.
Multiple virtual inheritance is not good, either - it adds a pointer
to each class, which consumes memory and time, that is resolved at
run-time. We want our classes to be fast, that's why we have used
templates.
The second approach is we can do something like this: mulcalculator<divcalculator<vector>
>. This is better, we operate on the same data, we have no
additional pointers. But the real problem, that I will try show you how to
solve is that now the mulcalculator<divcalculator<vector>
>::itr class has two pointers to data, that we suppose are the
same.
The solution is quite complicated. It bases on the fact that nested
types can be redefined and made available to child classes. What we do is:
- Make two additional classes: one with pointer to double, and one
empty, without any pointers, just fake one. Both should have default
constructors (this can be modified, I will write about it later).
- There should be template helper class defined, that will inherit
from template parameter, and the only thing it enhances is new nests
with typedef new type - the one with real pointer to double. Let's
suppose the name of the nested type is doublepointerclass.
- In each class that needs itr to have pointer to double, in
our case both divcalculator and mulcalculator, the
redefined itr class should inherit both from parent's itr
class and the class denoted by doublepointerclass member. In
the main class doublepointerclass member should be redefined to
denoted the class with fake pointer to double. If class doesn't need
the extended itr class, just do nothing with it.
- Now we can use data member in constructor of redefined itr,
whatever happens, it will be there, and there will be only one copy of
it.
Remark: make sure you have zero-length base classes option
checked on. In other case, you will get many unused bytes of memory
wasted.
So, we can modify our original class hierarchy in this way:
Two classes holding pointer to double:
class doublepointer {
public:
double *p;
};
class doublepointerfake {
};
Helper class - ancestor for other classes:
template<class C>
class strct2calc : public C {
public:
typedef
doublepointer doublepointerclass;
};
The new divcalculator class:
template<class C>
class divcalculator : public C {
public:
class itr;
typedef doublepointerfake doublepointerclass;
double
getvalue(int);
itr
getitr(int no, double data);
}
And it's itr class:
template<class C>
class divcalculator<C>::itr : public C::itr,
C::doublepointerclass {
public:
divcalculator<C>::itr(double *p)
{ data = p; }
double
getdivvalue() { return operator*() / data[ operator*() ]; }
};
The class mulcalculator should be modified in similar way.
Where it is used
I used it in my NoNameLib, in
the implementation of iterators. It's because sometimes iterator has to
have pointer to container it is iterating trough. And in my library there
is inheritance similar to the shown here, that new classes inherit from
it's template parameter and expand it's functionality. Sometimes more than
one of such classes in a single branch of inheritance needs expanded
iterators. And that's why I had to make this up.
Well, I know, this is quite complicated, but if you will look into it,
I guess you understand, what I wanted to say. If not, email
me.
|