Reputation: 179
I'm writing an intrusive linked list
class ListAlgorithm {
ListNode& next(ListNode& n) {
//returns an object of type ListNode linked to n.
}
};
Users usually want to add some features (such as some additional data) on ListNode like this:
class UserNode : public ListNode {
void operationOnUserData();
int userData;
};
Then users have to downcast ListNode returned by 'next' into UserNode. It is inconvenient. Thus, I tried to make ListAlgorithm a template class :
//U extends ListNode
template<class U>
class ListAlgorihtm {
U& next(U& u);
};
But then I have to upcast u into ListNode inside the method 'next' because class U could accidentally hide some members of ListNode that ListAlgorithm uses. This is error-prone because I could forget the upcast and compiler will not warn about that. I have to downcast ListNode into U again for the return value but it is safe because 'next' takes an instance u of U and the return value is something from u.
Another trial is
//U extends ListNode
template<class U>
class ListAlgorhtm {
U& next(ListNode& n);
};
In this case, the upcast problem is not there, but I have to downcast ListNode into U for the return value and it is not safe because it is not sure that n is an instance of U. It could be an instance of another type extending ListNode.
What is the best solution in this case? I think this is a very elementary design problem and I'd like to know what kind of material I have to study for basic OO design like this.
Upvotes: 1
Views: 193
Reputation: 1967
Your actual problem here is that you allow users to subclass ListNode
and mess with its semantics by adding arbitrary data and operations to ListNode
objects through subclassing. This therefore makes it necessary for the user to interpret the ListNode&
return values of actual ListNode
methods as something that those return values are not, semantically speaking.
This problem of a semantic nature is reflected in how tedious your code suddenly becomes, with casts and templating of an unrelated class (ListAlgorithm
) which is due to your problem "propagating" and infecting other parts of your code.
Here's a solution: a ListNode
object should not be allowed to also be a UserNode
object. However, it should be allowed to have, to carry with it a UserData
object that can be retrieved and manipulated.
In other words, your list becomes a simple container template, like std::list
, and the users can specify the operations and data members that they need as part of the definition of the class they use as the template argument.
class IListNode
{
public:
// whatever public methods you want here
protected:
// pure virtual methods maybe?
};
class ListNode : public IListNode
{
// List node class, no data
};
template<class UserDataType>
class ListNodeWithData : public IListNode
{
private:
UserDataType data;
public:
ListNodeWithData <UserDataType>(UserDataType &data) :
data(data)
{ }
const UserDataType& getData() {
return data;
}
};
class ListAlgorithm
{
public:
template<class UserDataType>
ListNodeWithData<UserDataType>& next(const ListNodeWithData<UserDataType>& node) {
// Do stuff
}
ListNode& next(const ListNode& node) {
// Do stuff, which may be very similar to the stuff done above
// in which case you may want to prefer to just define the
// method below, and remove this one and the one above:
}
// You should define either this method or the two above, but having
// them all is possible too, if you find a use for it
IListNode& next(const IListNode& node) {
// Do generic stuff
}
};
As far as the size of the resulting classes is concerned, I just know it will increase if you use virtual methods in IListNode.
Upvotes: 1
Reputation: 2234
As far as the issue you raise goes, any time you want to operate on members of a class and avoid hiding by a derived class, just make sure your operations are on the base, so
template<class U>
class ListAlgorihtm {
public:
U& next(U& u) {
return static_cast<U&>(return nextNode(u));
}
private:
ListNode& nextNode(ListNode& n);
};
That said, you have a lot of options for this problem set. The Boost library has an "intrusive" library that embeds node information either as base_hook
(as a base of the user data) or member_hook
(as a member of the class, which avoids some of the problems you describe). Check it out at http://www.boost.org/doc/libs/1_57_0/doc/html/intrusive.html.
Upvotes: 0