Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent writing clone method for each subclass

Tags:

c++

Here is my case:

class A
{
public:
    virtual A* clone() = 0;
};

class B : public A
{
    virtual B* clone() override
    { 
        return new B();
    }
};

In my code now 100% of sublcasses of A just implement the clone method exactly the same way only for class D I have return type D and I create object of D of course.

How can I prevent this duplication? What patterns or tricks can help?

I have thought to use CRTP pattern, but it uses templates, and my function is virtual. As we understand templates and virtual functions are incompatible.

NVI pattern does not work either, as return type of clone methods are different.

like image 693
Narek Avatar asked Oct 20 '22 17:10

Narek


1 Answers

[class.virtual]/8:

If the class type in the covariant return type of D::f differs from that of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D.

So CRTP cannot work perfectly, since the template argument which refers to the derived class would be an incomplete type, and thus covariant return types are difficult to simulate.

However, here's a try:

class A
{
public:
    virtual A* clone() = 0;
};

namespace detail {
    template <typename T>
    struct Cloner : A {
        using A::A; // For constructors
        virtual A* clone() override {return new T;}
    };
}

template <typename T>
struct Cloner : detail::Cloner<T> {
    using detail::Cloner<T>::Cloner; // For constructors

    // For callers 
    template <class=void>
    T* clone() {return static_cast<T*>(detail::Cloner<T>::clone());}
};

class B : public Cloner<B>
{
};

Demo.

Note two things:

  • The overload of clone returning a pointer to the derived class type is a function template. That's so we don't override the virtual function clone: If we would, the code would be ill-formed, because the return type is not covariant (see above).

  • Because clone is a function template, to ensure that it get's called at all, we can to let it hide the virtual clone function. That's achieved via inheritance: Names in derived classes hide names in base classes. Thus if we call through a B-pointer/reference, we get the appropriate return type (B*), since the name is looked up in ::Cloner before detail::Cloner.

like image 147
Columbo Avatar answered Oct 22 '22 20:10

Columbo