Home > Software engineering >  Polymorphism with templated class
Polymorphism with templated class

Time:10-21

I created a Foo class which needs to be used only as a shared pointer, so I made the constructor private to prevent client to use Foo directly:

#include <memory>

class Foo {
public:
    static std::shared_ptr<Foo> Create() { 
        return std::shared_ptr<Foo>(new Foo());
    }
    
    std::shared_ptr<Foo> Copy() {
        return std::shared_ptr<Foo>(new Foo(*this));
    }

private:
    Foo() = default;
};

I then updated Foo to use the CRTP pattern so I could have a class Bar inherit from it without having to re-define the Create() and Copy() methods:

#include <memory>

template <class Self>
class Foo {
public:
    template <class... Args>
    static std::shared_ptr<Self> Create(Args&&... args) {
        return std::shared_ptr<Self>(new Self(std::forward<Args>(args)...));
    }

    virtual std::shared_ptr<Self> Copy() {
        return std::shared_ptr<Self>(new Self(*this));
    }

private:
    Foo() = default;
};

class Bar : public Foo<Bar> {};

Then I needed to provide a non-templated FooBase interface that would provide some functions shared by all derived classes:

#include <memory>

class FooBase {
public:
    virtual void DoSomething() =0;
};

template <class Self>
class Foo : public FooBase {
public:
    template <class... Args>
    static std::shared_ptr<Self> Create(Args&&... args) {
        return std::shared_ptr<Self>(new Self(std::forward<Args>(args)...));
    }

    virtual std::shared_ptr<Self> Copy() {
        return std::shared_ptr<Self>(new Self(*this));
    }

    virtual void DoSomething() override { }

private:
    Foo() = default;
};

class Bar : public Foo<Bar> {
public:
    virtual void DoSomething() override { }
};

Now I also need to expose the Copy() method to the Foobar interface, but I cannot find any elegant ways to do it without changing the name for the interface (e.g. CopyBase())

#include <memory>

class FooBase {
public:
    virtual void DoSomething() =0;

    // Expose Copy method by using a different name.
    virtual std::shared_ptr<FooBase> CopyBase() const =0;
};

template <class Self>
class Foo : public FooBase {
public:
    template <class... Args>
    static std::shared_ptr<Self> Create(Args&&... args) {
        return std::shared_ptr<Self>(new Self(std::forward<Args>(args)...));
    }

    virtual std::shared_ptr<FooBase> CopyBase() const override {
        return Copy();
    }

    virtual std::shared_ptr<Self> Copy() {
        return std::shared_ptr<Self>(new Self(*this));
    }

    virtual void DoSomething() override { }

private:
    Foo() = default;
};

class Bar : public Foo<Bar> {
public:
    virtual void DoSomething() override { }
};

This code works, but I feel that there must be a better way to do this. I exposed my thinking process in details as I suspect that the flaws might be in the design, so I'm wondering if anyone could help me gain a different perspective on this issue.

Thanks for your time!

CodePudding user response:

covariant return type is only possible with regular pointer or reference, not with smart pointer.

Additional issue with CRTP is that class is incomplete, so covariance cannot be used neither.

The traditional way is indeed to split the clone in 2 part, a virtual one (private) and a public (non-virtual) one, something like:

template <typename Derived, typename Base>
class Clonable : public Base
{
public:
    template <class... Args>
    static std::shared_ptr<Derived> Create(Args&&... args) {
        return std::shared_ptr<Derived>(new Derived(std::forward<Args>(args)...));
    }
    std::shared_ptr<Derived> Clone() const {
        return std::shared_ptr<Derived>(static_cast<Derived*>(vclone()));
    }
private:
    // Cannot use covariant-type `Derived*` in CRTP as Derived is not complete yet
    Base* vclone() const override {
        return new Derived(static_cast<const Derived&>(*this));
    }
};

And then

class FooBase {
public:
    virtual ~FooBase() = default;
    virtual void DoSomething() = 0;

    // Expose Copy method by using a different name.
    std::shared_ptr<FooBase> Clone() const { return std::shared_ptr<FooBase>(vclone()); }

private:
    virtual FooBase* vclone() const = 0;
};

class Bar : public Clonable<Bar, FooBase> {
public:
    void DoSomething() override { }
};

Demo

  • Related