在我们的遗留项目中,我们有一个函数,它引用基类并在堆上创建派生类的副本。这基本上是这样解决的: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 将会失败,但我不知道是否可以以我想要的方式访问实际类型。 任何帮助将不胜感激。
答案 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
。