Let's say I'm working on a library that works on items of type Item. The main entry point is a class like:
class Worker
{
private:
SomeContainer _c;
public:
void add( const Item &i );
void doSomething();
};
The doSomething()
method looks at the added items, compares them, etc. and does something with them. So the Item class needs an operator==
.
Now, I want to use this library in different environments and in each environment the implementation of the Item class is different. Therefore, Item needs a virtual comparison function:
class Item
{
protected:
virtual bool _equals( Item &other ) = 0;
public:
bool operator==( Item &other ) { return _equals( other ); };
};
And each environment has its own Item implementation. The library only knows about the Item class and the specific item classes are defined and implemented in the platform-specific applications using the library. In environment A it might be:
class AItem: public Item
{
private:
bool _equals( Item &other );
std::string _member;
...
};
and in environment B:
class BItem: public Item
{
private:
bool _equals( Item &other );
int _member;
...
};
What is now the best way to, for each environment, implement the comparison for use by the library? _equals
is specified in the Item class , so its specific item implementations need to cast other
to their own type.
In a given environment, different item types will not be used at the same time, so given that assumption, the following would be sort-of safe:
bool AItem::_equals( Item &other )
{
return this->_member == static_cast<AItem &>(other)._member;
}
But it seems like a nasty solution because it allows a programmer using the library to implement a new environment to break things if he adds items of different types to the worker.
Other solutions I can think of are:
dynamic_cast
.other
is of the same type. Not very elegant.#define
at compile-time. This means the library itself must be extended for each environment.But I feel there must be a more elegant solution. Maybe something completely different. What would you do?
Wouldn't it better if Worker was templatized on Item instead of relying on inheritance? In general value semantic (and equality is at the earth of value semantic) doesn't work well with inheritance. In your specific example, I also fear truncation if SomeContainer does copy what it stored in it.
If you really need an operation which depend on the dynamic type of two objects, you should do search about "multiple dispatch" (or look in "Modern C++ Design", it has a chapter on this subject). There are several known techniques for this with different trade-off. One of the best known one is often associated with the Visitor Pattern. The simplest variant depend on knowing beforhand all the descendants of Item. (If you look at a description of the visitor pattern, take into account that both hierarchies of that pattern would be unified for your application).
Edit: here is the sketch of an example:
class ItemA;
class ItemB;
class Item
{
public:
virtual bool equal(Item const&) const = 0;
protected:
virtual bool doesEqual(ItemA const*) const { return false; }
virtual bool doesEqual(ItemB const*) const { return false; }
};
class ItemA: public Item
{
public:
virtual bool equal(Item const& other) const
{
return other.doesEqual(this);
}
protected:
virtual bool doesEqual(ItemA const* other) {
return do_the_check;
}
};
class ItemB: public Item
{
public:
virtual bool equal(Item const& other) const
{
return other.doesEqual(this);
}
protected:
virtual bool doesEqual(ItemB const* other) {
return do_the_check;
}
};
bool operator==(Item const& lhs, Item const& rhs)
{
return lhs.equal(rhs);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With