C++ 克隆习语中协变返回类型的用处?

Posted

技术标签:

【中文标题】C++ 克隆习语中协变返回类型的用处?【英文标题】:Usefulness of covariant return types in C++ clone idiom? 【发布时间】:2010-11-16 22:10:52 【问题描述】:

通常的克隆习惯使用协变返回类型:

struct Base 
    virtual Base* clone();
;

struct Derived : public Base 
    Derived* clone();
;

我读到的内容是协变返回类型是后来添加到 C++ 中的,旧的编译器可能不支持它们。在这种情况下,Derived 类必须声明其clone 成员函数以返回Base*。因为,大概我在使用这个习惯用法时只通过Base指针和/或引用访问Derived对象,那么声明返回类型Derived*的真正用途/好处是什么?

还有一个相关的问题:

我更喜欢使用智能指针来表达clone 签名的所有权转移语义。这在使用协变返回类型时是不可能的,因为auto_ptr<Derived>auto_ptr<Base> 不协变。 (请注意,我不是在寻找关于使用智能指针的讲座——auto_ptr 只是在这里用作示例)。那么在这种情况下,有什么理由不让Derived 返回auto_ptr<Base>?有没有更好的方式来表达所有权转移语义?

【问题讨论】:

你应该看看这里:***.com/questions/196733/… 我想知道“通常的克隆习惯使用协变返回类型”的说法。我个人觉得很麻烦。 @John -- 有什么麻烦? 【参考方案1】:

因为,大概,我在使用这个习语时只通过基指针和/或引用访问派生对象......

你猜错了。仅仅因为存在基类并不意味着您将始终使用它。如果我有一个带有子类CircleRectangleShape 类,并且我还有一个Window 类。当我可以使用Rectangle 时,我不会使用Shape* 作为窗口的位置和大小。

对于所有权转移,无论如何您都不应该使用智能指针。我知道你不想听讲座,但我并不是说智能指针不好,你只是不应该在clone() 中使用它们。首先,您不能转让没有所有权的东西的所有权。如果有人想要auto_ptr,那么他们应该使用克隆的原始指针构造它。其他智能指针类型也是如此。

【讨论】:

“什么时候我可以使用矩形” - 所以我的所有窗户都必须是矩形的,这是 你的 的错吗?使用Shape*! 好吧,在这种情况下,我认为你已经在有毒设计的海洋中游泳了。此外,不确定您关于“不应该在clone 中使用[智能指针]”的论点是否真的成立。它与任何所有权转移方案一样有效,不是吗? clone 函数创建(并因此拥有)新对象,直到它返回给调用者。 @Tabber:该任务没有理想的智能指针,只有unique_ptr 具有所需的语义,但这需要符合 C++0x 的编译器(移动语义)。 auto_ptr 最好尽可能不用,其他类型的智能指针会引入过多的开销。 @Tabber33:认真的吗?您认为界面层次结构是“有毒设计”吗?如果是这样,那为什么不完成这个想法,完全停止使用动态多态呢? @Steve:当然不是——只要 OO 是有毒的设计。在我看来,“我将直接使用派生类,只是因为我碰巧能够,而我真的应该使用正确的接口类。”但我知道有时需要直接访问派生类,这就是协变返回类型有用的地方(在克隆后保存向下转换)。【参考方案2】:

当您有一个指向 Derived 的指针并想要获得它的克隆时,它很有用:

Derived *ptr = ...;
Derived *clone = ptr->clone();

如果没有协变返回类型,您必须进行显式转换:

Derived *clone2 = (Derived*)ptr->clone();

请注意,Derived 可能是更多派生类的基类,在这种情况下更有意义。

很遗憾,auto_ptr<Derived>auto_ptr<Base> 不是协变的。因此,在这种情况下,您必须从所有克隆函数返回相同的类型。

【讨论】:

@ybungalobill:最后一句中的结论不正确,尽管智能指针确实不是协变的(根据 C++ 规则)。模拟协方差很简单。例如,请参阅 my blog posting 在 c++ 中实现可重用克隆功能。干杯, @Alf 博客文章的摘要 - 有一个返回指针的虚函数(用协变返回类型覆盖),以及一个返回智能指针的公共非虚函数(在每个派生类中重载) .这样,调用者总是得到与他们调用函数的静态类型相对应的智能指针。在 Alf 的示例中,虚函数是受保护的,尽管我没有立即明白为什么它不是私有的。 @Steve:我同意private 可能会更好,但是我不确定从克隆返回智能指针,主要是因为它不遵循Clonable 的概念提升。 @Alf:是的,我知道。我自己使用这种技术。 @Matthieu:我觉得没问题——如果你想实现Clonable,添加一个调用虚函数的免费好友函数new_clone。返回智能指针满足一种风格,其中auto_ptr 返回值用于“记录”新资源的所有权,如果调用者想要使用任何其他智能指针类型,他们release() 它。我更喜欢只返回指针并让调用者负责将其放入他们选择的智能指针中,并且对于 C++0x,它总是unique_ptr 而不是auto_ptr,所以有问题的风格应该改变。

以上是关于C++ 克隆习语中协变返回类型的用处?的主要内容,如果未能解决你的问题,请参考以下文章

JAVA中的协变与逆变

java逆变与协变(待完善)

在 C++ 中使用成员函数向量时,有没有办法实现协变返回类型?

王垠的这道Java高级面试题,看似容易,其实很难

CLR 协变逆变

覆盖虚函数协变返回类型(两个指针)