Конструкция Force класса исключительно внутри завода - PullRequest
2 голосов
/ 11 июля 2019

Я хотел бы знать, знает ли кто-нибудь о способе заставить иерархию классов быть конструируемой только фабрикой, фактически запрещая прямое использование std::make_shared вне этой фабрики.

В приведенном ниже примере у меня Node в качестве базового класса и SceneNode в качестве одного из многих производных классов. Узел содержит статическую функцию-член create () , которая должна быть фабрикой и единственным способом создания новых экземпляров Node -приведенных классов.

#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
}

Ответы [ 2 ]

2 голосов
/ 11 июля 2019

Классический способ сделать вашу фабрику единственным классом, способным создать экземпляр данного класса, - сделать конструктор вашего класса приватным и сделать вашего фабричного друга вашим классом:

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

РЕДАКТИРОВАТЬ (с некоторым наследованием):

#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;
}
1 голос
/ 11 июля 2019

Одним из решений этой проблемы является создание типа, который может быть создан только фабрикой, и для создания базового типа необходим экземпляр этого класса. Вы можете установить соглашение, в котором первый аргумент конструктора для типов, производных от Node, является значением или ссылкой на тот тип, который передается конструктору Node. Поскольку никто другой не может иметь NodeKey, пользователи не могут создавать экземпляры всего, что происходит из Node, не пройдя фабрику.

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