Björn M.P.
Björn M.P.

Reputation: 165

Force class construction exclusively inside factory

I wanted to know if anyone knows of a way to force a class hierarchy to be constructible only by the factory, effectively prohibiting the direct use of std::make_shared outside of that factory.

In the example below I have Node as the base class and SceneNode as one of the many derived classes. Node contains a static member function create() which should be the factory and only way to create new instances of Node-derived classes.

#include <iostream>
#include <memory>

class Node {
  public:
    template <class T, class... Args>
    static std::shared_ptr<T> create(Args&&... args)
    {
      static_assert(std::is_base_of<Node, T>::value, "T must derive from Node");
      std::shared_ptr<T> node = std::make_shared<T>(std::forward<Args>(args)...);
      return node;
    }

  protected:
    Node() {}

};

class SceneNode : public Node {
  public:
    SceneNode() : Node()
    {
    }
};

int main() {
    auto a = Node::create<SceneNode>(); // Should be the only way
    auto b = std::make_shared<SceneNode>(); // Should be forbidden
}

Upvotes: 2

Views: 971

Answers (2)

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29022

One solution to this problem is to create a type that only the factory can instantiate, and have an instance of that class be required to construct the base type. You can establish a convention where the first constructor argument for types that derive from Node is a value of or reference to that type which is fed to Node's constructor. Since it's not possible for anyone else to have a NodeKey users can't instantiate anything that derives from Node without going through the factory.

#include <memory>
#include <utility>

// Class that can only be instantiated by the factory type
class NodeKey {
private:
    NodeKey() {};
    friend class Factory;
};

class Factory {
public:
    template<class T, class ... Args>
    auto make(Args&&... args) {
        auto ptr = std::make_shared<T>(NodeKey{}, std::forward<Args>(args)...);
        // Finish initializing ptr
        return ptr;
    }
};

class Node {
public:
    // Can only be called with an instance of NodeKey
    explicit Node(const NodeKey &) {};
};

class Foo : public Node {
public:
    // Forwards the instance 
    explicit Foo(const NodeKey & key) : Node(key) {};
};

int main()
{
    Factory factory;
    auto f = factory.make<Foo>();
}

Upvotes: 1

Adrien Givry
Adrien Givry

Reputation: 965

The classic way of making your factory the only class able to instanciate a given class is to make your class constructor private, and making your factory friend of your class:

class Foo
{
    friend class FooFactory;

private:
    Foo() = default;
};

class FooFactory
{
public:
    static Foo* CreateFoo() { return new Foo(); }
    static void DestroyFoo(Foo* p_toDestroy) { delete p_toDestroy; }
};

int main()
{
    // Foo foo; <== Won't compile
    Foo* foo = FooFactory::CreateFoo();
    FooFactory::DestroyFoo(foo);
    return 0;
}

EDIT (With some inheritance):

#include <type_traits>

class Foo
{
    friend class FooBaseFactory;

protected:
    Foo() = default;
};

class Bar : public Foo
{
    friend class FooBaseFactory;

protected:
    Bar() = default;
};

class FooBaseFactory
{
public:
    template <typename T>
    static T* Create()
    {
        static_assert(std::is_base_of<Foo, T>::value, "T must derive from Foo");
        return new T();
    }

    template <typename T>
    static void Destroy(T* p_toDestroy)
    { 
        static_assert(std::is_base_of<Foo, T>::value, "T must derive from Foo");
        delete p_toDestroy;
    }
};

int main()
{
    // Foo foo; <== Won't compile
    Foo* foo = FooBaseFactory::Create<Foo>();
    FooBaseFactory::Destroy<Foo>(foo);

    // Bar bar; <== Won't compile
    Bar* bar = FooBaseFactory::Create<Bar>();
    FooBaseFactory::Destroy<Bar>(bar);
    return 0;
}

Upvotes: 2

Related Questions