Reputation:
Consider the case of having two pure virtual classes, say X
and Y
. Y
specifies a pure virtual method that returns a smart pointer to an instance of X
(e.g. virtual unique_ptr<X> getX() const = 0
). This is done so that subclasses of Y
can return whatever implementation of X
they desire.
However, this means that a user has to be aware that upon calling getX()
, they should expect to work with an instance of unique_ptr<X>
(not ideal). This is readily fixed by wrapping unique_ptr<X>
in a class as in the following example:
#include <iostream> // cout, endl
#include <memory> // unique_ptr
using namespace std;
////////////////////////////////////////////////////////////////////////////////
struct X {
virtual void exampleMethod() = 0;
};
struct XCloneable : public X {
typedef unique_ptr<XCloneable> P;
virtual P clone() const = 0;
};
class XWrapper : public X {
XCloneable::P p;
public:
XWrapper(XCloneable::P p) noexcept : p(move(p)) {}
XWrapper(const XWrapper &that) noexcept : p(that.p->clone()) {}
XWrapper &operator=(const XWrapper &that) noexcept {
p = that.p->clone();
return *this;
}
XWrapper(XWrapper &&that) noexcept : p(move(that.p)) {
}
XWrapper &operator=(XWrapper &&that) noexcept {
p = move(that.p);
return *this;
}
virtual void exampleMethod() { p->exampleMethod(); }
};
////////////////////////////////////////////////////////////////////////////////
struct XX : public XCloneable {
virtual void exampleMethod() { cout << "XX" << endl; }
virtual XCloneable::P clone() const { return XCloneable::P(new XX); }
};
////////////////////////////////////////////////////////////////////////////////
struct Y {
virtual XWrapper getX() = 0;
};
struct YY {
virtual XWrapper getX() { return XWrapper(XCloneable::P(new XX)); }
};
////////////////////////////////////////////////////////////////////////////////
int main() {
YY yy;
auto x = yy.getX();
x.exampleMethod();
return 0;
}
However, this is quite verbose, and has to be written for each pure virtual class similar to X
. I imagine it would not be too difficult to automatically generate wrappers such as the above systematically, although I would prefer to not run my code through anything other than the usual C preprocessor (although more exotic preprocessing solutions are welcome/interesting).
Is there a way to handle this scenario systematically?
Upvotes: 2
Views: 342
Reputation: 12410
The pattern you're talking about is an Abstract Factory. I'm really not sure why most of the code in your question exists though... Here is an example of an abstract factory implementation which should do what you need:
#include <iostream>
#include <memory>
class X
{
public:
virtual void exampleMethod() = 0;
};
class MyX : public X
{
public:
void exampleMethod() override
{
std::cout << "Calling MyX::exampleMethod()";
}
};
class XFactory
{
public:
virtual std::unique_ptr<X> createX() = 0;
static XFactory* getInstance()
{
return m_instance.get();
}
static void setInstance(std::unique_ptr<XFactory> instance)
{
m_instance = move(instance);
}
private:
static std::unique_ptr<XFactory> m_instance;
};
std::unique_ptr<XFactory> XFactory::m_instance = std::unique_ptr<XFactory>();
class MyXFactory : public XFactory
{
public:
std::unique_ptr<X> createX() override
{
return std::unique_ptr<X>(new MyX);
}
};
int main()
{
// Call setInstance with different XFactory implementations to get back
// different implementations of X.
std::unique_ptr<XFactory> xFactory(new MyXFactory);
XFactory::setInstance(move(xFactory));
std::unique_ptr<X> x = XFactory::getInstance()->createX();
x->exampleMethod();
return 0;
}
This example outputs:
Calling MyX::exampleMethod()
I don't see that you need a wrapper at all although there is no reason you couldn't return one from MyXFactory::createX()
as long as it extends X
.
EDIT:
I just re-read your question, why is it not ideal for the caller to know they are dealing with a unique_ptr? I would think that it is the most ideal. By giving them a unique_ptr you are explicitly saying to them: you own this now.
Upvotes: 1