【问题标题】:Best way to handle a virtual method for copying a polymorphic object if its base class should not be abstract?如果它的基类不应该是抽象的,那么处理复制多态对象的虚拟方法的最佳方法是什么?
【发布时间】:2020-07-03 05:41:13
【问题描述】:

我需要复制具有基指针的多态类的对象。我知道我可以为此实现一个虚拟方法。但是如果基类不应该是抽象的呢?如果您忘记在派生中重新实现它,那么在没有纯说明符的情况下保留该方法可能会导致运行时错误。不舒服。处理这个问题的最佳方法是什么?

【问题讨论】:

  • 我想也许一定有一个特性会强迫你实现一个方法,即使它不是纯的。
  • 把它变成一个纯函数。然后创建另一个声明为 final 的类,该类继承并植入该函数。
  • 虚拟克隆方法是最好的做法。
  • @Mellester 你知道这个类不应该是抽象的,是吗?如果有,你能举个例子吗?
  • 只在基类和派生类中实现虚拟复制功能有什么问题?

标签: c++ copy polymorphism clone


【解决方案1】:

您永远不应该实例化基类,这是有充分理由的。 如果您确实需要创建一个空的最终类,请使用以下内容。

class IBase 
{
    virtual void SharedCode()
    {
        1 + 1;
        /// code here
    };
    virtual void AbstractDecalration() = 0;
};

class Base final: IBase
{
     void AbstractDecalration() override;
};

Base b{};

所有未来派生类都将能够使用 IBase 的 SharedCode,并且您将拥有一个最终的 Base 实例化类。这是为了将来验证您的代码库。

但是我意识到这不是你问的问题,所以这里是一个实现,我使用一个简单的检查类的 vtable 指针来查看我是否有正确的类。

这是一个运行时检查,不能跨库使用 dynamic_assert 如果是这种情况。

#include <memory>
#include <type_traits>
#include <assert.h>
class Base {
    public:
       auto clone() const
   {
      return std::unique_ptr<Base>(this->clone_impl());
   }
    private:
     virtual Base* clone_impl() const
     {
         Base b{};
         int* bVtablePtr = (int*)((int*)&b)[0];
         int* thisVtablePtr = (int*)((int*)this)[0];
         assert(bVtablePtr == thisVtablePtr);
        return new Base(*this);
    }
};

class Derived : public Base 
{
       auto clone() const
   {
      return std::unique_ptr<Derived>(this->clone_impl());
   }
    virtual Derived* clone_impl() const
    {
        return new Derived();
    }
};
class Falty : public Base{};

    int  main(){
        std::unique_ptr<Derived> good(new Derived());
        std::unique_ptr<Falty> falty(new Falty());
        good->clone(); // oke
        falty->clone(); // this function asserts at runtime
        
}

注意私有 clone_impl 和 public unique_ptr 重新调整克隆方法。 对于防止代码中的内存泄漏非常有用

【讨论】:

    【解决方案2】:

    您可以通过引入另一个抽象基类以及对clone 函数使用CRPT 来实现您想要的。然后,clone 将“免费”在所有派生类中自动实现(无需手动重新输入)。示例:

    struct Abstract
    {
      virtual ~Abstract() {}
      virtual Abstract* clone() const = 0;
      virtual void say() const = 0;
    };
    
    template <typename B, typename D>
    struct AbstractCloneable : B
    {
      virtual B* clone() const override
      {
        return new D(static_cast<const D&>(*this));
      }
    };
    
    // original base class
    struct Base : AbstractCloneable<Abstract, Base>
    {
      virtual void say() const override
      {
        std::cout << "Base" << std::endl;
      }
    };
    
    // original derived class #1
    struct Derived1 : AbstractCloneable<Base, Derived1>
    {
      virtual void say() const override
      {
        std::cout << "Derived1" << std::endl;
      }
    };
    

    还有一个测试程序:

    int main()
    {
      std::unique_ptr<Abstract> ptr1 = std::make_unique<Base>();
      ptr1->say();
      std::unique_ptr<Abstract> ptr1_copy{ ptr1->clone() };
      ptr1_copy->say();
    
      std::unique_ptr<Abstract> ptr2 = std::make_unique<Derived1>();
      ptr2->say();
      std::unique_ptr<Abstract> ptr2_copy{ ptr2->clone() };
      ptr2_copy->say();
    }
    

    哪些输出:

    Base
    Base
    Derived1
    Derived1
    

    现场演示:https://godbolt.org/z/3FeSTd


    更多细节和解释请看这篇文章:C++: Polymorphic cloning and the CRTP (Curiously Recurring Template Pattern)

    【讨论】:

    • 我喜欢它在继承链中的工作方式。是否可以使用 private 或 protected 来防止某人从派生类继承,除非使用此模板。
    • @Mellester 目前不确定,我现在没有时间深入挖掘。但这可能是一个有趣的独立问题(如果尚未发布)。
    猜你喜欢
    • 1970-01-01
    • 2018-05-23
    • 2012-11-29
    • 1970-01-01
    • 2011-12-19
    • 2010-12-02
    • 1970-01-01
    • 2018-03-29
    • 2011-06-05
    相关资源
    最近更新 更多