Oleksa
Oleksa

Reputation: 665

Best way to handle a virtual method for copying a polymorphic object if its base class should not be abstract?

I need to copy an object of a polymorphic class having a base pointer. I know that I can implement a virtual method for this. But what if the base class should not be abstract? Leaving the method without pure-specifier can lead to run-time bugs, if you forget reimplement it in the derived. It's uncomfortable. What is the best way to handle this?

Upvotes: 2

Views: 224

Answers (2)

Daniel Langr
Daniel Langr

Reputation: 23497

You can achieve what you want by introducing another abstract base class plus using CRPT for clone function. Then, clone will be automatically implemented in all derived classes "for free" (without manual retyping). Example:

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;
  }
};

And a test program:

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();
}

Which outputs:

Base
Base
Derived1
Derived1

Live demo: https://godbolt.org/z/3FeSTd


See this article for more details and explanations: C++: Polymorphic cloning and the CRTP (Curiously Recurring Template Pattern).

Upvotes: 1

Mellester
Mellester

Reputation: 940

There are good reasons why you should never want to instantiate a base class. If you do need to make a empty final class use the following.

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

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

Base b{};

All Future Derived classes will be able to use the SharedCode of IBase and you will have a Instantiated class of Base that is final. This is for future proofing your code base.

However I realize that is not the question you asked so here is a implementation were I use a simple check to the vtable pointer of the class to see if I have the correct class.

This is a runtime check and doesn't work across libraries use dynamic_assert if that is the case.

#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
        
}

Note the private clone_impl and public unique_ptr retuning clone method. Very usefull to prevent memory leaks in your code

Upvotes: 2

Related Questions