In general, covariance allows you to express more information in the derived class interface than is true in the base class interface. The behaviour of a derived class is more specific than that of a base class, and covariance expresses (one aspect of) the difference.
It's useful when you have related hierarchies of gubbins, in situations where some clients will want to use a base class interface, but other clients will use the derived class interface. With const-correctness omitted:
class URI { /* stuff */ };
class HttpAddress : public URI {
bool hasQueryParam(string);
string &getQueryParam(string);
};
class Resource {
virtual URI &getIdentifier();
};
class WebPage : public Resource {
virtual HttpAddress &getIdentifier();
};
Clients which know they have a WebPage (browsers, perhaps) know that it's meaningful to look at query params. Clients which are using the Resource base class know no such thing. They will always bind the returned HttpAddress&
to a URI&
variable or temporary.
If they suspect, but don't know, that their Resource object has an HttpAddress, then they can dynamic_cast
. But covariance is superior to "just knowing" and doing the cast for the same reason that static typing is useful at all.
There are alternatives - stick the getQueryParam
function on URI
but make hasQueryParam
return false for everything (clutters the URI interface). Leave WebPage::getIdentifier
defined to return URL&
, actually returning an HttpIdentifier&
, and have callers do a pointless dynamic_cast
(clutters the calling code, and the documentation of WebPage where you say "the URL returned is guaranteed to be dynamically castable to HttpAddress"). Add a getHttpIdentifier
function to WebPage
(clutters the WebPage
interface). Or just use covariance for what it's meant to do, which is express the fact that a WebPage
doesn't have an FtpAddress
or a MailtoAddress
, it has an HttpAddress
.
Finally there is of course a reasonable argument that you shouldn't have hierarchies of gubbins, let alone related hierarchies of gubbins. But those classes could just as easily be interfaces with pure virtual methods, so I don't think it affects the validity of using covariance.