【问题标题】:When is C++ covariance the best solution?C++ 协方差何时是最佳解决方案?
【发布时间】:2010-11-18 15:31:09
【问题描述】:

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

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

【问题讨论】:

  • 当我读到这个问题时,我的反应是完全一样的。有什么问题 - 不要使用协变返回类型!

标签: c++ covariance return-type


【解决方案1】:

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

 obj = obj->copy();

不管 obj 的类型是什么。

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

【讨论】:

  • 但是你不应该(恕我直言)关心类型是什么 A * a = some_a_or_b->clone();也可以正常工作,然后您可以在克隆指针上使用其他虚拟方法。
  • 我认为协方差更多地是语法糖,然后是必要的特性。你总是可以做 A* a = b->clone(); dynamic_cast(a)->initB();
  • 除了协方差类型是静态检查的。
  • IMO,任何类型的演员阵容都是丑陋而笨拙的,应该尽可能避免。使用 dynamic_cast 这里没有编译时检查 B::clone 确实按照方法名称的含义执行并返回 B*(而不是指向 A 的某个其他子类的指针)。
  • 这个例子对 Java 残缺的类型系统很有意义,但我仍然看不出在 C++ 中的相关性。
【解决方案2】:

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

当您有相关的 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 函数粘贴在 URI 上,但让 hasQueryParam 对所有内容都返回 false(使 URI 接口混乱)。将WebPage::getIdentifier 定义为返回URL&,实际上返回HttpIdentifier&,并让调用者执行毫无意义的dynamic_cast(使调用代码混乱,并且您说“返回的URL 保证是动态的”的WebPage 文档可转换为 HttpAddress")。将getHttpIdentifier 函数添加到WebPage(使WebPage 界面混乱)。或者只是使用协方差来表示它的意思,即表示WebPage 没有FtpAddressMailtoAddress,它有一个HttpAddress

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

【讨论】:

  • 古宾斯?这不是我熟悉的词。谷歌搜索它从 UrbanDictionary.com 产生:一个 gubbin 是有时表现得像个流浪汉的人。 “你不会相信他的父母拥有一座豪宅吧?这个小鬼!”我认为在编程上下文中有不同的含义?
  • '不怕。它只是表示“东西”、“事物”。
【解决方案3】:

我认为在声明返回特定类而不是其基类的工厂方法时,协方差可能很有用。 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;
    }
    ...
};

【讨论】:

    【解决方案4】:

    在处理现有代码时,我经常发现自己使用协方差来摆脱 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();
    

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 大概你 dynamic_cast 指针来计算你实际得到的具体实例?在这种情况下,您不需要协方差。还是我错过了什么?
      • 'dynamic_cast' 要求类型是多态的,并且可能很昂贵。但是,使用工厂类中的函数模板也可以实现相同的结果。这将执行两项任务:检查类型是否相关(isbaseclass 或其他),然后将泛型结果向下转换为派生类型。无需动态转换。
      • 潜在的昂贵我的意思是比 static_cast 更昂贵,即使这样,它也只有在你调用它数百万次的地方才会有所作为。 ;)
      • 你也可以想象一个访问者喜欢的模式。但是在我使用的情况下,是在工厂内部使用了协方差。显然,工厂知道自己的动态类型(我还没有在工厂中使用过两级继承:-()
      • @Richard:我认为 Scott Meyers 曾经指出,在 VS 中使用 dynamic_cast 可以让您进行字符串比较。 (IIRC;这就是涉及 DLL 的时候。)这肯定比 static_cast 慢几个数量级。
      【解决方案6】:

      它在您想要使用混凝土工厂来生成混凝土产品的场景中变得很有用。你总是想要最专业的、足够通用的接口……

      使用具体工厂的代码可以安全地假设产品是具体产品,因此它可以安全地使用具体产品类提供的关于抽象类的扩展。这确实可以算是语法糖,不过还是很甜的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-13
        • 2020-04-12
        • 1970-01-01
        • 2014-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多