34

这个问题是几个小时前在这里提出的,让我意识到我从未在自己的代码中真正使用过协变返回类型。对于那些不确定协方差是什么的人,它允许(通常)虚函数的返回类型不同,前提是这些类型是相同继承层次结构的一部分。例如:

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

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

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

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

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

4

6 回答 6

36

典型的例子是.clone()/.copy()方法。所以你总是可以做

 obj = obj->copy();

不管 obj 的类型是什么。

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

于 2009-08-11T14:37:51.770 回答
15

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

当您有相关的 gubbins 层次结构时,它很有用,在某些客户端想要使用基类接口但其他客户端将使用派生类接口的情况下。省略 const 正确性:

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

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

如果他们怀疑但不知道他们的 Resource 对象有一个 HttpAddress,那么他们可以dynamic_cast. 但是协方差优于“只知道”并进行强制转换,原因与静态类型完全有用的原因相同。

还有其他选择 - 坚持使用该getQueryParam功能,URIhasQueryParam对所有内容都返回 false(使 URI 界面混乱)。保留WebPage::getIdentifier定义为 return URL&,实际上返回一个HttpIdentifier&,并让调用者做一个毫无意义的事情dynamic_cast(混乱的调用代码,以及你说“返回的 URL 保证可动态转换为 HttpAddress”的 WebPage 的文档)。添加一个getHttpIdentifier功能WebPage(使WebPage界面混乱)。或者只是使用协方差来表示它的意思,即表示 aWebPage没有 anFtpAddress或 a MailtoAddress,它有一个HttpAddress.

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

于 2009-08-11T16:04:54.480 回答
5

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

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;
    }
    ...
};
于 2009-08-11T15:01:48.050 回答
2

在处理现有代码以摆脱 static_casts 时,我经常发现自己使用协方差。通常情况是这样的:

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

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

于 2009-08-19T15:14:25.627 回答
0

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

于 2009-08-11T14:46:23.487 回答
0

在您想使用混凝土工厂生产混凝土产品的场景中,它变得很有用。您总是想要最通用的最专业的界面......

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

于 2009-08-11T15:08:06.073 回答