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