使用 C++ 和 glfw 的 raii 架构
raii architecture using c++ and glfw
我目前正在尝试使用 glfw 用 C++ 编写一个小型引擎来创建 window。我想充分利用 raii 来提出一个异常安全的架构,并使引擎几乎不可能以错误的方式使用。
但我目前正在为一些问题而苦苦挣扎,我觉得我错过了一些重要的东西,所以我在这里问,提前谢谢大家!
假设我有一个名为 glfw_context
的基本 raii
结构,它在 ctor
中调用 glfwInit()
并在 dtor
中调用 glfwTerminate()
.它还接受一个 error-callback
并在调用 glfwInit()
之前设置它,但我会省略这些细节,因为我想专注于基本问题。
我还创建了一个 class glfw_window
,它在 ctor
中需要一个 const glfw_context&
,而重载的 ctor
采用相同类型的参数,但带有 glfw_context&&
是 deleted
.
这背后的想法是,当上下文是 rvalue
时,它只会在 ctor
调用期间存在,但 glfwTerminate()
会在所有 windows 调用之前被调用正确销毁(这发生在 glfw_window
class 的 dtor
中)。
我为 glfw_window
正确实施了 moves
,而 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, ... );
}
在这种情况下,上下文仍然不会超过 window。
有解决这个问题的好方法吗?我不想把它作为一个要求放在文档中(像 "the context must outlive each window" 这样的东西似乎对我来说并不适用)。
我目前的方法是使用 std::shared_pointer< glfw_context >
作为 glfw_window
的 ctor
的参数并将其存储为成员。然而,这仍然没有解决我的问题,因为我仍然可以 make_shared< glfw_context >()
不同的上下文并将它们传递给不同的 windows。并且由于只有第一个分配的实例会调用 glfwTerminate()
我仍然可以引发上下文在所有 windows.
之前被破坏的情况
那么解决这类问题的正确方法是什么?我可以构建一个无论用户如何尝试(错误)使用它都能在这里正常工作的漂亮架构吗?
我的其他一些想法包括 glfw_context
中的 private ctor
和 static
工厂方法与 shared_pointer
方法相结合,但这感觉很像 singleton
和我怀疑这是处理问题的最佳方式。
您可能会使用单例的变体:
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 */
};
那么只要你的实例至少有一个"reference",CreateContext()
returns它,否则它会创建一个新的。
不可能有 2 个不同的 glfw_context
实例
我目前正在尝试使用 glfw 用 C++ 编写一个小型引擎来创建 window。我想充分利用 raii 来提出一个异常安全的架构,并使引擎几乎不可能以错误的方式使用。 但我目前正在为一些问题而苦苦挣扎,我觉得我错过了一些重要的东西,所以我在这里问,提前谢谢大家!
假设我有一个名为 glfw_context
的基本 raii
结构,它在 ctor
中调用 glfwInit()
并在 dtor
中调用 glfwTerminate()
.它还接受一个 error-callback
并在调用 glfwInit()
之前设置它,但我会省略这些细节,因为我想专注于基本问题。
我还创建了一个 class glfw_window
,它在 ctor
中需要一个 const glfw_context&
,而重载的 ctor
采用相同类型的参数,但带有 glfw_context&&
是 deleted
.
这背后的想法是,当上下文是 rvalue
时,它只会在 ctor
调用期间存在,但 glfwTerminate()
会在所有 windows 调用之前被调用正确销毁(这发生在 glfw_window
class 的 dtor
中)。
我为 glfw_window
正确实施了 moves
,而 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, ... );
}
在这种情况下,上下文仍然不会超过 window。
有解决这个问题的好方法吗?我不想把它作为一个要求放在文档中(像 "the context must outlive each window" 这样的东西似乎对我来说并不适用)。
我目前的方法是使用 std::shared_pointer< glfw_context >
作为 glfw_window
的 ctor
的参数并将其存储为成员。然而,这仍然没有解决我的问题,因为我仍然可以 make_shared< glfw_context >()
不同的上下文并将它们传递给不同的 windows。并且由于只有第一个分配的实例会调用 glfwTerminate()
我仍然可以引发上下文在所有 windows.
那么解决这类问题的正确方法是什么?我可以构建一个无论用户如何尝试(错误)使用它都能在这里正常工作的漂亮架构吗?
我的其他一些想法包括 glfw_context
中的 private ctor
和 static
工厂方法与 shared_pointer
方法相结合,但这感觉很像 singleton
和我怀疑这是处理问题的最佳方式。
您可能会使用单例的变体:
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 */
};
那么只要你的实例至少有一个"reference",CreateContext()
returns它,否则它会创建一个新的。
不可能有 2 个不同的 glfw_context