Архитектура raii с использованием C++ и glfw

В настоящее время я пытаюсь написать небольшой движок на C++, используя glfw для создания окон. Я хочу активно использовать raii, чтобы создать архитектуру, исключающую исключительные ситуации, и сделать движок практически невозможным для неправильного использования. Но в настоящее время я борюсь с некоторыми проблемами, и мне кажется, что мне не хватает чего-то важного, поэтому я спрашиваю здесь, спасибо всем заранее!

Предположим, у меня есть базовый raii-структура называется glfw_context что вызывает glfwInit() в ctor а также glfwTerminate() в dtor, он также принимает error-callback и устанавливает его перед вызовом glfwInit() но я опущу такие детали, так как хочу сосредоточиться на основных проблемах. я также создал класс glfw_window это требует const glfw_context& в ctor тогда перегруженный ctor принимая те же аргументы, но с glfw_context&& является deleted,

идея заключалась в том, что, когда контекст был rvalue это будет существовать только во время ctor позвони но glfwTerminate() будет вызван до того, как все окна будут должным образом уничтожены (что происходит в dtor из glfw_window учебный класс). я реализовал moves правильно для glfw_window тогда как glfw_context не может быть ни скопирован, ни перемещен. но проблемы начинаются здесь: нет никакого способа, которым я мог бы мешать пользователю создавать несколько glfw_context экземпляров. так что я пошел с static член теперь, так как glfw не предоставляет что-то вроде glfwIsInit(), которая решает эту проблему для области моей структуры (только самый старый экземпляр будет вызывать glfwTerminate()) но это все еще не защищает пользователя от написания кода, подобного этому:

std::vector< glfw_window > windows;
{
    // introduce explicit scope to demonstrate the problem
    glfw_context context{};
    windows.emplace_back( context, ... );
}

в этом случае контекст все равно не переживет окно.

есть хороший способ решить это? я не хочу помещать это в документацию как требование (что-то вроде "контекст должен пережить каждое окно", просто кажется, что это не подходит мне).

мой нынешний подход заключается в использовании std::shared_pointer< glfw_context > в качестве аргумента glfw_window"s ctor и сохранить его как член. однако это все еще не решает мои проблемы, так как я все еще мог make_shared< glfw_context >() разные контексты и передать их в разные окна. и так как только первый выделенный экземпляр будет вызывать glfwTerminate() я все еще могу провоцировать ситуации, когда контекст был разрушен перед всеми окнами.

так каков правильный подход к такой проблеме? Могу ли я построить хорошую архитектуру, которая работает здесь правильно, независимо от того, как пользователь пытается (неправильно) использовать ее? некоторые другие мои мысли включают в себя private ctor в glfw_context и static заводской метод в сочетании с shared_pointer-подход, но это очень похоже на singleton и я сомневаюсь, что это лучший способ приблизиться к вещам.

1 ответ

Решение

Вы можете использовать вариант синглтона:

class glfw_context
{
    glfw_context() {/*Your impl*/}

    glfw_context(const glfw_context&) = delete;
    glfw_context& operator=(const glfw_context&) = delete;
public:
    friend std::shared_ptr<glfw_context> CreateContext()
    {
        static std::weak_ptr<glfw_context> instance;

        auto res = instance.lock();
        if (res == nullptr) {
            res = std::make_shared<glfw_context>();
            instance = res;
        }
        return res;
    }

    /* Your impl */
};

Тогда, пока есть хотя бы одна "ссылка" на ваш экземпляр, CreateContext() возвращает его, иначе он создает новый.
Нет возможности иметь 2 разных экземпляра glfw_context

Другие вопросы по тегам