可克隆的类层次结构和 unique_ptr

Posted

技术标签:

【中文标题】可克隆的类层次结构和 unique_ptr【英文标题】:Clonable class hierarchy and unique_ptr 【发布时间】:2016-06-13 11:10:29 【问题描述】:

我对可克隆抽象类和唯一指针有疑问。假设我有以下可克隆的抽象基类

class Base
 
    public:
        virtual void doSomething()=0;
        virtual std::unique_ptr<Base> clone() const=0;

以及提供附加方法的派生抽象类

class Derived : public Base
 
    public:
        virtual void doSomething()=0;
        virtual void doSomethingMore()=0;
        virtual std::unique_ptr<Base> clone() const=0;

这允许定义新的类,这些类通过组合存储基本层次结构的多态对象。比如

class Composed
 
    public:
        Composed(Base const &base_) : basePtr(base_.clone()) 

    private:
        std::unique_ptr<Base> basePtr;

通过这种方式,我应该能够在 Composed 中存储 Derived 类型的对象,而无需对 Derived 添加 wrt Base 的方法进行切片。但是,我想定义另一个存储从 Derived 继承的多态对象的对象,将其视为 Derived 类型的对象。使用与上面相同的结构

class ComposedDerived
 
    public:
        ComposedDerived(Derived const &derived_) : derivedPtr(derived_.clone()) 

    private:
        std::unique_ptr<Derived> derivedPtr;

很明显,我得到一个编译错误,因为 Derived 的 clone 方法返回一个 std::unique_ptr&lt;Base&gt;。另一方面,如果我将 Derived 的定义更改如下

class Derived : public Base
 
    public:
        virtual void doSomething()=0;
        virtual void doSomethingMore()=0;
        virtual std::unique_ptr<Derived> clone() const=0;

在这种情况下,编译器会给出以下错误:invalid covariant return type for ‘virtual std::unique_ptr&lt;Derived&gt; Derived::clone() const

有没有办法让编译器理解std::unique_ptr&lt;Derived&gt;实际上可以通过多态性用作std::unique_ptr&lt;Base&gt;,而不是争论派生类clone方法的返回类型?

【问题讨论】:

derivedPtr(static_cast&lt;Derived*&gt;(derived_.clone().release())) 怎么样? 这似乎是一个聪明的方法。你能解释一下.release()吗?据我了解,derived_.clone() 返回一个std::unique_ptr&lt;Base&gt; 对象。 .release() 让这个 unique_ptr 释放已在堆中创建的 Derived 对象的所有权,并返回一个 Base* 对象。最后,我将此指针转换为用于初始化derivedPtrDerived* 指针。对吗? 【参考方案1】:

clone() 方法使用NVI(非虚拟接口惯用语),如下所示:

class Base
 
    public:
        virtual void doSomething()=0;

        std::unique_ptr<Base> clone() const 
            return cloneImpl();
        

    private:
        virtual std::unique_ptr<Base> cloneImpl() const=0;
;

class Derived : public Base
 
    public:
        virtual void doSomething()=0;
        virtual void doSomethingMore()=0;

        std::unique_ptr<Derived> clone() const 
            return std::unique_ptr<Derived>(static_cast<Derived*>(cloneImpl().release()));
        
;

您甚至可以在子类中添加“覆盖”clone() 方法的更多安全性和便利性,如下所示:

class Base
 
    public:
        virtual void doSomething()=0;

        std::unique_ptr<Base> clone() const 
            return checkedClone<Base>();
        

    protected:
        template<class T>
        std::unique_ptr<T> checkedClone() const 
            auto p = cloneImpl();
            assert(typeid(*p) == typeid(*this) && "subclass doesn't properly override cloneImpl()");
            assert(nullptr != dynamic_cast<T*>(p.get()));
            return std::unique_ptr<T>(static_cast<T*>(p.release()));
        

    private:
        virtual std::unique_ptr<Base> cloneImpl() const=0;
;

class Derived : public Base
 
    public:
        virtual void doSomething()=0;
        virtual void doSomethingMore()=0;

        std::unique_ptr<Derived> clone() const 
            return checkedClone<Derived>();
        
;

【讨论】:

这很有趣。因此,如果我理解正确,我只需要在继承自BaseDerived 的具体类中实现virtual std::unique_ptr&lt;Base&gt; cloneImpl() const 方法。对吗? 是的。但是,如果您还需要能够轻松地克隆具体类的对象,那么您也可以“覆盖”clone() 函数,类似于在 Derived 中的实现方式。 所以在Derived 类中我可以同时拥有std::unique_ptr&lt;Derived&gt; clone() const 方法和std::unique_ptr&lt;Base&gt; clone() const 方法。是不是因为Base 类中的clone() 方法不再是抽象的,所以我可以重写它? 是的,但请注意,在这种情况下,“覆盖”不是一个正确的术语(这就是我引用它的原因)。实际上Derived::clone() 隐藏了Base::clone()(通过Derived 对象访问时)。【参考方案2】:

如何利用协变回报:

class Base  
public:
    std::unique_ptr<Base> clone() const 
        return std::unique_ptr<Base>(cloneImpl());
    
    virtual ~Base();
private:
    virtual Base* cloneImpl() const;
;

class Derived : public Base  
public:
    std::unique_ptr<Derived> clone() const 
        return std::unique_ptr<Derived>(cloneImpl());
    
    ~Derived() override;
private:
    // Covariant return:
    Derived* cloneImpl() const override;
;

【讨论】:

使用这种方法,我必须在每个继承自BaseDerived 的类中实现cloneImpl() 方法。是对的吗? @Leon 提出的 NVI 的优缺点是什么?感谢您的帮助 优点是不需要static_cast。 想象一下,在Derived 类中,我想同时拥有std::unique_ptr&lt;Base&gt; clone() const 方法和std::unique_ptr&lt;Derived&gt; clone() const 方法。我怎样才能使用这种技术实现这一目标? 你确实拥有它们。然而,只有后者是可见的。前者是隐藏的。您可以使用第二个: foo.Base::clone() 是您想要的。但是,您可能并不真正需要它,因为 std::unique_ptr&lt;Derived&gt; 可以转换为 std::unique_ptr&lt;Base&gt; 太棒了。最后一个问题...在Derived* cloneImpl() const override,为什么我需要override 关键字?那是因为编译器无法将此方法与从 Base 继承的 virtual Base* cloneImpl() const; 区分开来吗?非常感谢

以上是关于可克隆的类层次结构和 unique_ptr的主要内容,如果未能解决你的问题,请参考以下文章

C# (Galaga Style Project) - 基于层次结构限制 Projectile Prefab 克隆

即使父视图可访问,子视图也不显示在层次结构中

实体框架中的类和接口层次结构?

如何制作俄罗斯方块克隆?

原型模式

IfcSpatialElement