Search code examples
c++factory-pattern

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
}

Solution

  • 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>();
    }