C ++协方差何时是最佳解决方案?

时间:2009-08-11 14:32:30

标签: c++ covariance return-type

几个小时前在这里问过{p> This question让我意识到这一点 我从来没有在我自己的代码中实际使用过协变返回类型。对于那些 不确定协方差是什么,它允许(通常)虚拟的返回类型 如果类型是同一继承的一部分,则函数不同 层次结构。例如:

struct A {
   virtual ~A();
   virtual A * f();
   ...
};

struct B : public A {
   virtual B * f();
   ...
};

两个f()函数的不同返回类型被认为是协变的。较旧版本的C ++要求返回类型相同,因此B必须如下所示:

struct B : public A {
   virtual A * f();
   ...
};

所以,我的问题是:有没有人有一个真实世界的例子,其中需要协变返回类型的虚函数,或者产生一个优秀的解决方案来简单地返回基指针或引用?

6 个答案:

答案 0 :(得分:29)

规范示例是.clone() / .copy()方法。所以你总是可以做到

 obj = obj->copy();

无论obj的类型是什么。

编辑:此克隆方法将在Object基类中定义(因为它实际上是在Java中)。因此,如果clone不是协变的,你要么必须强制转换,要么限制为root基类的方法(只有很少的方法,比较副本的源对象的类)。

答案 1 :(得分:12)

通常,协方差允许您在派生类接口中表达比基类接口中更多的信息。派生类的行为比基类的行为更具体,协方差表示(差异的一个方面)。

当您拥有gubbins的相关层次结构时,在某些客户端希望使用基类接口但其他客户端将使用派生类接口的情况下,它很有用。省略了const-correctness:

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();
};

知道他们有WebPage(也许是浏览器)的客户知道查看查询参数是有意义的。使用资源基类的客户端不知道这样的事情。他们将始终将返回的HttpAddress&绑定到URI&变量或临时变量。

如果他们怀疑,但不知道他们的Resource对象有一个HttpAddress,那么他们可以dynamic_cast。但协方差优于“只是知道”和执行演员,原因与静态输入完全相同。

还有其他选择 - 将getQueryParam功能贴在URI上,但使hasQueryParam对所有内容都返回false(使URI接口混乱)。保留WebPage::getIdentifier定义为返回URL&,实际返回HttpIdentifier&,并让调用者执行无意义dynamic_cast(使调用代码混乱,并将WebPage文档放在哪里说“返回的URL保证可动态转换为HttpAddress“)。向getHttpIdentifier添加WebPage函数(使WebPage接口混乱)。或者只是使用协方差来表达它的意图,这表明WebPage没有FtpAddressMailtoAddress,它有一个HttpAddress

最后,当然有一个合理的论据,你不应该有gubbins的层次结构,更不用说gubbins的相关层次结构了。但是那些类可以很容易地与纯虚方法接口,所以我认为它不会影响使用协方差的有效性。

答案 2 :(得分:4)

我认为协方差在声明返回特定类而不是其基类的工厂方法时非常有用。 This article很好地解释了这种情况,并包含以下代码示例:

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;
    }
    ...
};

答案 3 :(得分:1)

在使用现有代码摆脱static_cast时,我经常发现自己使用协方差。通常情况类似于:

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() {...}
};    

这允许我

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

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

而不是

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

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

现在遇到这样的代码时,人们可能会争论原始设计以及是否有更好的设计 - 但根据我的经验,通常存在诸如优先级,成本/收益等限制,这些限制会干扰广泛的设计美化并且仅允许像这样的小步骤。

答案 4 :(得分:0)

另一个例子是一个具体的工厂,它将返回指向具体类而不是抽象类的指针(当工厂必须构造复合对象时,我已将它用于工厂内部使用)。

答案 5 :(得分:0)

在您希望使用具体工厂生成具体产品的场景中,它非常有用。你总是想要最通用的界面......

代码使用具体工厂可以安全地假设产品是具体产品,因此它可以安全地使用具体产品类提供的关于抽象类的扩展。这确实可以被视为语法糖,但无论如何它都很甜蜜。