Архитектура 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