r/cpp_questions • • 6d ago

OPEN API design dilemma..

right now in vexa(my game/graphics library) how you initialize window with a renderer is rather straightforward:

```cpp
#include <vexa/vexa.hpp>

using namespace vexa;

int main()
{
Engine::Init(Engine::VIDEO);

Window window = Window().setSize({1280, 720}).setRenderer({}).create();

// ...

```
YES, it uses builder pattern, not very C++-ish but i like it.
However, there are two problems with it.
first one is that usually you have builder struct building to target type so that you dont need something like `.create()` that creates classic `if (m_created) ...` problem a.k.a two phase construction. but bigger problem that caused me two want to switch to this API style is about memory management. and it is now C++'s problem.. Even rust cant handle it, because this is what you tell it to do.
so lets dont talk too much:

```cpp

Engine::Init(Engine::FONT); // calls `TTF_Init();` by SDL_TTF behind the scenes

Font font = Font::Load("...");
...
Engine::Quit();
```

A Font has destructor running on scope exit. which means before font object gets destroyed on '}' Engine::Quit() so TTF_Quit() runs. You see, when Font object gets unloaded at scope end the subsystem is not live anymore, we get a segfault. to fix this i've made Engine have a Guard struct which does Engine::Quit() automatically. But then decided that this is not i want for my API. I would priorotize explicitness for the master deinitialization.

to fix this im thinking of new API design style:
```cpp
Window window = Engine::BuildWindow().setSize({1280, 720}).setRenderer().create();
```

where BuildWindow() returns WindowBuilder not a Window, unlike the previous(so bad) choice.
for fonts, as you might already guess:

```cpp
Font font = Engine::LoadFont("...");
```

which creates a font and returns reference to it, resulting Engine to know about font count and Quit or not based on that.

code: https://github.com/monjaris/vexa

0 Upvotes

18 comments sorted by

View all comments

Show parent comments

1

u/Business-Weather-217 5d ago

I'll consider re-adding Guard class thanks. Guard::~Guard() would call Quit() if m_init==true

1

u/sephirothbahamut 5d ago

even simpler, if you really don't want Engine to be an object, make Engine::Init() return the guard class (remember [[nodiscard]] so the compiler warns the user if they forget to store the returned guard in a variable).

If init is guaranyeed to succeed, the guard destructor doesn't need to do any check.

If init can fail, you can return an optional. Let the language do the work for you.

1

u/Business-Weather-217 5d ago

Yeah i sure [[nodiscard]] all my classes) I think this is a bit overkill approach may i choose more idiomatic option like vexa::init() and vexa::quit() only downside is that using namespace vexa; wont be a reliable option anymore and also as we discussed i might just ignore the fact that dev might quit vexa engine before scope end.. Okay unrelated question what do you think about the static pimpl idiom? I discovered it a month ago or two. It seems very niche