从基类指针克隆派生类

时间:2021-01-27 09:48:55

标签: c++

在我们的遗留项目中,我们有一个函数,它引用基类并在堆上创建派生类的副本。这基本上是这样解决的:https://godbolt.org/z/9ooM4x

#include <iostream>

class Base
{
public:
    virtual Base* vclone() const = 0;
    int a{7};
};

class Derived : public Base
{
public:
    Derived() 
    {
        a = 8; 
    }

    Base* vclone() const override
    {
        return new Derived(*this);
    }
};

Base* clone(const Base& original)
{
    return original.vclone();
}

int main()
{
    Derived d1;;
    auto* d2 = clone(d1);

    std::cout << d2->a << std::endl;
}

这可行,但我想摆脱我们必须在每个派生类中使用的样板 vclone 方法。

我们有数百个派生类,其中一些不是直接从 Base 派生的,而是从其他一些派生类派生的。因此,如果我们忘记覆盖 vclone 方法,我们甚至可能不会收到即将发生切片的警告。

现在,关于这样的设计有很多话要说,但这是 10-15 年前的代码,我尝试逐步对其进行现代化改造。我所寻找的是不依赖于虚拟方法的克隆的模板化版本。我想要的是这样的克隆函数:

Base* clone(const Base& original)
{
    return new <Actual Derived Type>(original);
}

实际的派生类型在某种程度上是已知的,因为如果尝试使用错误的类型强制转换为 dynamic_cast 将会失败,但我不知道是否可以以我想要的方式访问实际类型。 任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:1)

我还认为您可能无法改进代码以使其更短。 我想说这个实现基本上是要走的路。

您可以做的是将 Derived::clone 的返回值更改为 Derived *。是的,C++ 允许这样做。

然后直接使用 Derived::clone 会产生正确的指针类型,而 Base::clone 仍然按预期工作

class Derived : public Base
{
public:
    Derived() 
    {
        a = 8; 
    }

    Derived* vclone() const override  // <<--- 'Derived' instead of 'Base'. 
    {
        return new Derived(*this);
    }
};

我还将将 vclone 成员函数重命名为 clone(不需要有两个名称)。

可以将自由函数 clone 制作为模板,以便它适用于所有类并返回正确的指针类型

template <class T>
T *clone(const T *cls)
{
  return cls->clone();
}

然而,所有这些变化并没有使代码更短,只是更有用,也许更易读。

为了让它更短一些,您可以使用 CRTP 方法。

template <class Derived, class Base>
class CloneHelper: public Base {
    Derived* vclone() const override  
    {
        return new Derived(* static_cast<Derived *>(this) );
    }
};
// then use
class Derived : public CloneHelper<Derived, Base>
{
public:
    Derived() 
    {
        a = 8; 
    }
};

不过,我不确定这是否值得。仍然不能忘记 CloneHelper,它使继承始终为 public,并且您不能那么容易地委托给 Base 构造函数,而且它不那么明确。

答案 1 :(得分:0)

您可能希望在单独的类模板中实现 clone 函数,该函数仅在创建派生类的对象时应用于派生类。派生类不实现 clone(保持纯虚拟)以避免忘记在进一步的派生类中覆盖它。

示例:

struct Base {
    virtual Base* clone() const = 0;
    virtual ~Base() noexcept = default;
};

template<class Derived>
struct CloneImpl final : Derived {
    using Derived::Derived;

    CloneImpl* clone() const override { // Covariant return type.
        return new CloneImpl(*this);
    }
};

template<class T>
std::unique_ptr<T> clone(T const& t) { // unique_ptr to avoid leaks.
    return std::unique_ptr<T>(t.clone());
}

struct Derived : Base {};
struct Derived2 : Derived {};

int main() {
    CloneImpl<Derived> d1; // Apply CloneImpl to Derived when creating an object.
    auto d2 = clone(d1);
    auto d3 = clone(*d2);

    CloneImpl<Derived2> c1; // Apply CloneImpl to Derived2 when creating an object.
    auto c2 = clone(c1);
    auto c3 = clone(*c2);
}

有关在不重复代码的情况下实现接口层次结构的更多详细信息,请参阅 https://stackoverflow.com/a/16648036/412080

答案 2 :(得分:0)

您可以使用外部克隆函数和 typeid

#include <typeindex>
#include <string>
#include <stdexcept>
#include <cassert>

template<class Derived_t, class Base_t>
Base_t *clone_helper(Base_t *b) {
    return new Derived_t(*static_cast<Derived_t *>(b));
}

struct Base {
    virtual ~Base() = default;
};

struct Derived : Base {};

Base *clone(Base *b) {
    const auto &type = typeid(*b);
    if (type == typeid(Base)) {
        return clone_helper<Base>(b);
    }
    if (type == typeid(Derived)) {
        return clone_helper<Derived>(b);
    }

    throw std::domain_error(std::string("No cloning provided for type ") + typeid(*b).name());
}

int main() {
    Derived d;
    Base *ptr = &d;

    auto ptr2 = clone(ptr);

    assert(typeid(*ptr2) == typeid(Derived));
}

如果您没有提供克隆方法,这将在运行时找到。它可能比平时慢。遗憾的是,switch 是不可能的,因为我们无法在编译时获得类型的 typeid