仅使用基类指针复制派生实体(无需详尽的测试!) - C++

发布于 2024-10-18 00:15:09 字数 798 浏览 3 评论 0原文

给定一个由大量派生类继承的基类,以及一个需要您通过指向每个实体的基类指针来管理这些派生类的程序结构。当只知道基类指针时,是否有一种简单的方法来复制整个派生对象?

环顾四周,似乎可以(如果非常乏味)使用dynamic_cast调用来检查基指针是否可以转换为适当的派生类,然后使用派生类的复制构造函数复制它。然而,这并不是一个真正的最佳解决方案,部分原因是过度使用dynamic_cast,而且维护和扩展会很麻烦。

我遇到的另一个更优雅的解决方案如下:

class Base
{
public:
   Base(const Base& other);
   virtual Base* getCopy();
   ...
}

class Derived :public Base
{
   Derived(const Derived& other);
   virtual Base* getCopy();
   ...
}

Base* Base::getCopy()
{
   return new Base(*this));
}

Base* Derived::getCopy()
{
   return static_cast<Base*>(new Derived(*this));
}

然后,通过在指向任何派生对象的基类指针上调用 getCopy() ,仍然可以获得基类指针,而且还可以获得整个派生对象已被复制。此方法感觉更易于维护,因为它只需要在所有派生类中都有一个类似的 getCopy() 函数,并且无需针对所有可能的派生对象进行测试。

本质上,这是明智的吗?还是有更好、更简洁的方法来做到这一点?

Given a base class that is inherited by plethora of derived classes, and a program structure that requires you manage these via base class pointers to each entity. Is there a simple way to copy the entire derived object when only the base class pointer is known?

Looking around it would seem possible (if incredibly tedious) to use the dynamic_cast call to check if a base pointer can be cast as the appropriate derived class, then copy it using the derived class's copy constructor. However this is not really an optimal solution partly due to the excessive use of dynamic_cast and also it would see a total headache to maintain and extend.

Another more elegant solution I have come across is as follows:

class Base
{
public:
   Base(const Base& other);
   virtual Base* getCopy();
   ...
}

class Derived :public Base
{
   Derived(const Derived& other);
   virtual Base* getCopy();
   ...
}

Base* Base::getCopy()
{
   return new Base(*this));
}

Base* Derived::getCopy()
{
   return static_cast<Base*>(new Derived(*this));
}

Then by calling getCopy() on the Base class pointer to any derived object one still gets a base class pointer back but also the whole of the derived object has been copied. This method feels a lot more maintainable as it just requires a similar getCopy() function to be in all derived classes, and does away with the need to test against all possible derived objects.

Essentially, is this wise? or is there a better, even neater way of doing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

薄暮涼年 2024-10-25 00:15:09

这种方法是复制多态对象的首选方法,因为它减轻了确定如何将任意类型的对象复制到该对象的责任,而不是尝试在编译时确定它。更一般地说,如果您不知道编译时基类指针指向什么,您就不可能知道需要执行哪些潜在代码片段才能获得正确的副本。因此,任何可行的解决方案都需要动态选择代码,而虚拟函数是实现此目的的好方法。

对您的实际代码的两条评论。首先,C++继承允许派生类重写基类成员函数,使派生函数返回比基类版本更具体的类型的指针。这称为协方差。例如,如果基类函数是

virtual Base* clone() const;

那么派生类可以将其重写为

virtual Derived* clone() const;

并且这将工作得很好。例如,这允许您拥有如下代码:

Derived* d = // something...
Derived* copy = d->clone();

如果没有协变重载,这将是不合法的。

另一个细节 - 在您的代码中,您显式地将派生指针static_cast指向代码中的基指针。这是完全合法的,但没有必要。 C++ 将隐式地将派生类指针转换为基类指针,而无需进行强制转换。但是,如果您使用协变返回类型的想法,则不会出现这种情况,因为返回类型将与您将创建的对象的类型匹配。

This approach is the preferred way of copying polymorphic objects because it offloads the responsibility of determining how to copy an object of an arbitrary type to that object, rather than trying to determine it at compile-time. More generally, if you don't know what the base class pointer points at at compile-time, you can't possibly know which of the many potential pieces of code you would need to execute in order to get a correct copy. Because of this, any working solution will need a dynamic selection of code, and the virtual function is a good way to do this.

Two comments on your actual code. First, C++ inheritance allows a derived class overriding a base class member function to have the derived function return a pointer of a type more specific than the base class version. This is called covariance. As an example, if a base class function is

virtual Base* clone() const;

Then a derived class can override it as

virtual Derived* clone() const;

And this will work perfectly fine. This allows you, for example, to have code like this:

Derived* d = // something...
Derived* copy = d->clone();

Which, without the covariant overload, wouldn't be legal.

Another detail - in the code you have, you explicitly static_cast the derived pointers to base pointers in your code. This is perfectly legal, but it's not necessary. C++ will implicitly convert derived class pointers to base class pointers without a cast. If, however, you use the covariant return type idea, this won't come up because the return type will match the type of the objects you'll be creating.

面如桃花 2024-10-25 00:15:09

请注意,您不需要那里的 static_cast 。 Derived* 隐式转换为 Base*。正如 Ken Wayne 所建议的那样,您绝对不应该为此使用dynamic_cast,因为具体类型在编译时是已知的,并且编译器可以告诉您是否不允许进行强制转换。

至于方法,这种模式足够标准,可以分别作为 ICloneable 和 Object.clone() 内置到 C# 和 Java 中。

编辑:

...或者有更好、更简洁的方法吗?

您可以使用“自参数化基类”,这样您就不必每次都实现clone()函数。您只需要实现复制构造函数:

#include <iostream>

struct CloneableBase {
    virtual CloneableBase* clone() const = 0;
};


template<class Derived>
struct Cloneable : CloneableBase {
    virtual CloneableBase* clone() const {
       return new Derived(static_cast<const Derived&>(*this));
    }
};


struct D1 : Cloneable<D1> {
    D1() {}
    D1(const D1& other) {
        std::cout << "Copy constructing D1\n";
    }
};

struct D2 : Cloneable<D2> {
    D2() {}
    D2(const D2& other) {
        std::cout << "Copy constructing D2\n";
    }
};


int main() {
    CloneableBase* a = new D1();
    CloneableBase* b = a->clone();
    CloneableBase* c = new D2();
    CloneableBase* d = c->clone();
}

Note that you don't need the static_cast there. Derived* converts to Base* implicitly. You absolutely shouldn't use a dynamic_cast for that, as Ken Wayne suggests, since the concrete type is known at compile time, and the compiler can tell you if the cast is not allowed.

As for the approach, this pattern is standard enough to be built in to C# and Java as ICloneable and Object.clone(), respectively.

Edit:

... or is there a better, even neater way of doing this?

You could use a "self-parameterized base class", which saves you implementing the clone() function each time. You just need to implement the copy constructor:

#include <iostream>

struct CloneableBase {
    virtual CloneableBase* clone() const = 0;
};


template<class Derived>
struct Cloneable : CloneableBase {
    virtual CloneableBase* clone() const {
       return new Derived(static_cast<const Derived&>(*this));
    }
};


struct D1 : Cloneable<D1> {
    D1() {}
    D1(const D1& other) {
        std::cout << "Copy constructing D1\n";
    }
};

struct D2 : Cloneable<D2> {
    D2() {}
    D2(const D2& other) {
        std::cout << "Copy constructing D2\n";
    }
};


int main() {
    CloneableBase* a = new D1();
    CloneableBase* b = a->clone();
    CloneableBase* c = new D2();
    CloneableBase* d = c->clone();
}
余生一个溪 2024-10-25 00:15:09

是的,你的想法就是要走的路。它还允许派生类选择是否希望执行深复制或浅复制。

我有一点(有点挑剔)供将来参考:就安全性而言,使用dynamic_cast 优于使用static_cast 进行多态转换。这只是引起我注意的事情之一。

Yeah, your idea is the way to go. It also allows the derived classes to choose whether they wish to perform a deep or shallow copy.

I have one (somewhat nit-picky) point for future reference: in terms of safety, using dynamic_cast is preferred to static_cast for polymorphic conversions. It's just one of those things that grabs my attention.

我乃一代侩神 2024-10-25 00:15:09
template <class T>
Base* CopyDerived(const T& other) {
  T* derivedptr = new T(other);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL)
    return baseptr;
  delete derivedptr;
  // If this is reached, it is because T is not derived from Base
  // The exception is just an example, handle in whatever way works best
  throw "Invalid types in call to Copy";
}

这只需要您希望复制的每个派生类中都有一个可公开访问的复制构造函数。

template <class T>
Base* CopyDerived(const T& other) {
  T* derivedptr = new T(other);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL)
    return baseptr;
  delete derivedptr;
  // If this is reached, it is because T is not derived from Base
  // The exception is just an example, handle in whatever way works best
  throw "Invalid types in call to Copy";
}

This only requires a publicly accessible copy constructor in each derived class you wish to copy.

雅心素梦 2024-10-25 00:15:09

这是旧的,但想知道为什么没有人考虑 CRTP,正是为了“多态复制构造”: https:/ /en.wikipedia.org/wiki/Curiously_recurring_template_pattern

从 Wiki 复制:

// Base class has a pure virtual function for cloning
class AbstractShape {
public:
    virtual ~AbstractShape () = default;
    virtual std::unique_ptr<AbstractShape> clone() const = 0;
};

// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape : public AbstractShape {
public:
    std::unique_ptr<AbstractShape> clone() const override {
        return std::make_unique<Derived>(static_cast<Derived const&>(*this));
    }

protected:
   // We make clear Shape class needs to be inherited
   Shape() = default;
   Shape(const Shape&) = default;
   Shape(Shape&&) = default;
};

// Every derived class inherits from CRTP class instead of abstract class

class Square : public Shape<Square>{};

class Circle : public Shape<Circle>{};

它之所以有效,是因为现在基类始终可以访问派生类成员,而无需覆盖每个派生类的 virtual ,这要归功于实例化的模板。

This is old but wonders why no one was thinking about CRTP, exactly for "Polymorphic copy construction": https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Copied from Wiki:

// Base class has a pure virtual function for cloning
class AbstractShape {
public:
    virtual ~AbstractShape () = default;
    virtual std::unique_ptr<AbstractShape> clone() const = 0;
};

// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape : public AbstractShape {
public:
    std::unique_ptr<AbstractShape> clone() const override {
        return std::make_unique<Derived>(static_cast<Derived const&>(*this));
    }

protected:
   // We make clear Shape class needs to be inherited
   Shape() = default;
   Shape(const Shape&) = default;
   Shape(Shape&&) = default;
};

// Every derived class inherits from CRTP class instead of abstract class

class Square : public Shape<Square>{};

class Circle : public Shape<Circle>{};

It works because now Base class always can access the derived class members without overriding virtual for each derived class, thanks to the templates instantiated.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文