When is C++ covariance the best solution?

后端 未结 6 709
温柔的废话
温柔的废话 2020-12-05 23:00

This question was asked here a few hours ago and made me realise that I have never actually used covariant return types in my own code. For those not sure what covariance is

相关标签:
6条回答
  • 2020-12-05 23:34

    The canonical example is a .clone()/.copy() method. So you can always do

     obj = obj->copy();
    

    regardless what obj's type is.

    Edit: This clone method would be defined in the Object base class (as it actually is in Java). So if clone wasn't covariant, you would either have to cast, or would be restricted to methods of the root base class (which would have only very few methods, compared the class of the source object of the copy).

    0 讨论(0)
  • 2020-12-05 23:47

    I think covariance can be useful when declaring factory methods that return a specific class and not its base class. This article explains this scenario quite well, and includes the following code example:

    class product
    {
        ...
    };
    
    class factory
    {
    public:
        virtual product *create() const = 0;
        ...
    };
    

    class concrete_product : public product
    {
        ...
    };
    
    class concrete_factory : public factory
    {
    public:
        virtual concrete_product *create() const
        {
            return new concrete_product;
        }
        ...
    };
    
    0 讨论(0)
  • 2020-12-05 23:50

    It becomes useful in the scenario where you want to use a concrete factory to generate concrete products. You always want the most specialized interface that is general enough...

    The code using the concrete factory can safely suppose that the products are concrete products, so it can safely use the extensions provided by the concrete product class with regard to the abstract class. This could indeed be regarded as syntactic sugar, but it's sweet anyway.

    0 讨论(0)
  • 2020-12-05 23:52

    I often find myself using covariance when working with existing code to get rid of static_casts. Usually the situation is similar to this:

    class IPart {};
    
    class IThing {
    public:
      virtual IPart* part() = 0;
    };
    
    class AFooPart : public IPart {
    public:
      void doThis();
    };
    
    class AFooThing : public IThing {
      virtual AFooPart* part() {...}
    };
    
    class ABarPart : public IPart {
    public:
      void doThat();
    };
    
    class ABarThing : public IThing {
      virtual ABarPart* part() {...}
    };    
    

    This allows me to

    AFooThing* pFooThing = ...;
    pFooThing->Part()->doThis();
    

    and

    ABarThing pBarThing = ...;
    pBarThing->Part()->doThat();
    

    instead of

    static_cast< AFooPart >(pFooThing->Part())->doThis();
    

    and

    static_cast< ABarPart >(pBarThing->Part())->doThat();
    

    Now when coming across such code one could argue about the original design and whether there is a better one – but in my experience there are often constraints such as priorities, cost/benefit etc. which interfere with extensive design beautification and permit only small steps such as this one.

    0 讨论(0)
  • 2020-12-05 23:53

    Another example is a concrete factory which would returns pointers to the concrete classes instead of the abstract one (I've used it for internal use in the factory when the factory had to construct compound objects).

    0 讨论(0)
  • 2020-12-05 23:57

    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.

    0 讨论(0)
提交回复
热议问题