可克隆的类层次结构和 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<Base>
。另一方面,如果我将 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<Derived> Derived::clone() const
。
有没有办法让编译器理解std::unique_ptr<Derived>
实际上可以通过多态性用作std::unique_ptr<Base>
,而不是争论派生类clone
方法的返回类型?
【问题讨论】:
derivedPtr(static_cast<Derived*>(derived_.clone().release()))
怎么样?
这似乎是一个聪明的方法。你能解释一下.release()
吗?据我了解,derived_.clone()
返回一个std::unique_ptr<Base>
对象。 .release()
让这个 unique_ptr 释放已在堆中创建的 Derived
对象的所有权,并返回一个 Base*
对象。最后,我将此指针转换为用于初始化derivedPtr
的Derived*
指针。对吗?
【参考方案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>();
;
【讨论】:
这很有趣。因此,如果我理解正确,我只需要在继承自Base
或Derived
的具体类中实现virtual std::unique_ptr<Base> cloneImpl() const
方法。对吗?
是的。但是,如果您还需要能够轻松地克隆具体类的对象,那么您也可以“覆盖”clone()
函数,类似于在 Derived
中的实现方式。
所以在Derived
类中我可以同时拥有std::unique_ptr<Derived> clone() const
方法和std::unique_ptr<Base> 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;
;
【讨论】:
使用这种方法,我必须在每个继承自Base
或Derived
的类中实现cloneImpl()
方法。是对的吗? @Leon 提出的 NVI 的优缺点是什么?感谢您的帮助
优点是不需要static_cast。
想象一下,在Derived
类中,我想同时拥有std::unique_ptr<Base> clone() const
方法和std::unique_ptr<Derived> clone() const
方法。我怎样才能使用这种技术实现这一目标?
你确实拥有它们。然而,只有后者是可见的。前者是隐藏的。您可以使用第二个: foo.Base::clone() 是您想要的。但是,您可能并不真正需要它,因为 std::unique_ptr<Derived>
可以转换为 std::unique_ptr<Base>
。
太棒了。最后一个问题...在Derived* cloneImpl() const override
,为什么我需要override
关键字?那是因为编译器无法将此方法与从 Base 继承的 virtual Base* cloneImpl() const;
区分开来吗?非常感谢以上是关于可克隆的类层次结构和 unique_ptr的主要内容,如果未能解决你的问题,请参考以下文章