1
\$\begingroup\$

I am using SDL2 for window management and rendering, but it can be a little verbose. And because of how SDL2 works under the hood, the SDL_Renderer is tied to image resource loading.

So, I am setting out to write a wrapper around the SDL initialization and window/renderer management.

The core of it is called SDLContext, which is responsible for initializing SDL, as well as being where the SDL_Window and SDL_Renderer will reside.

It is initialized with a helper struct, SDLOpts, which just holds any initialization values.

SDLOpts

struct SDLOpts {
 int flags = SDL_INIT_EVERYTHING;
 struct SDLWindowOpts {
 int flags = 0;
 int w = 600;
 int h = 480;
 } window_opts;
 struct SDLRendererOpts {
 int flags = 0;
 } renderer_opts;
};

SDLContext.h

struct SDL_Window;
struct SDL_Renderer;
class SDLContext {
public:
 explicit SDLContext(SDLOpts const& options) noexcept;
 ~SDLContext() noexcept;
 SDLWindow sdlWindow() const noexcept;
 SDLRenderer sdlRenderer() const noexcept;
private:
 SDLOpts options_ {};
 SDL_Window* sdlwindow_ {nullptr};
 SDL_Renderer* sdlrenderer_ {nullptr};
};

SDLContext.cpp

struct SDL_Window;
struct SDL_Renderer;
class SDLContext {
public:
 explicit SDLContext(SDLOpts const& options) noexcept;
 ~SDLContext() noexcept;
 SDLWindow sdlWindow() const noexcept;
 SDLRenderer sdlRenderer() const noexcept;
private:
 SDLOpts options_ {};
 SDL_Window* sdlwindow_ {nullptr};
 SDL_Renderer* sdlrenderer_ {nullptr};
};

One thing I am on the fence about is throwing in the constructor marked noexcept. I don't want program execution to continue if I can't initialize SDL/create a window/create a renderer, and I also want to avoid using an Init function (trying to make it fit into RAII). But, I don't see any other alternatives.

SDLWindow and SDLRenderer are pretty bare-boned right now. They just exist as a wrapper around all of the SDL_* functions that relate to SDL_Window or SDL_Renderer.

One decision I did make was to make their default constructor private, and mark SDLContext as a friend class. This way, the only way to create an SDLWindow or SDLRenderer is from within the SDLContext class. They should not be created outside of it, because there should only be one window and one renderer.

SDLRenderer.h

struct SDL_Renderer;
class SDLRenderer {
 friend class SDLContext;
public:
 ~SDLRenderer() = default;
 //...
 //Functions that draw or change the rendering properties (IE, color)
 //...
private:
 explicit SDLRenderer(SDL_Renderer* renderer) noexcept;
 std::experimental::observer_ptr<SDL_Renderer> sdlrenderer_ {nullptr};
};

SDLWindow.h

struct SDL_Window;
class SDLWindow {
 friend class SDLContext;
public:
 ~SDLWindow() noexcept = default;
 //...
 //Functions that modify the windows width and height
 //...
private:
 explicit SDLWindow(SDL_Window* window) noexcept;
 std::experimental::observer_ptr<SDL_Window> sdlwindow_ {nullptr};
};

Any suggestions to what I've done so far? Any alternatives to stopping program execution outside of throw in a noexcept / using an Init function? Is it sane to mark SDLContext as a friend class to SDLRenderer and SDLWindow in order to prevent them from being created wherever?

asked Aug 10, 2019 at 19:10
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Throwing in a noexcept function

This is legal, see https://stackoverflow.com/questions/39763401/can-a-noexcept-function-still-call-a-function-that-throws-in-c17. However, you already have doubts about it, so that's telling you it's probably not a very proper thing to do. Compilers will also warn about this.

If you want your program to terminate when it can't create a window, I would make this explicit, and instead of using throw, call std::terminate() explicitly.

Define a namespace for your library

All your classes and structs have names with SDL prefixed, for good reasons. However, you can make it more explicit to the compiler that you want everything in a separate namespace. The advantage is that within that namespace, you don't have to use the prefix anymore. For example, you can write:

namespace SDL {
 class Window {
 friend class Context;
 public:
 ~Window() noexcept = default;
 ...
 };
 class Context {
 public:
 explicit Context(Opts const &options) noexcept;
 ~Context() noexcept;
 Window Window() const noexcept;
 Renderer Renderer() const noexcept;
 private:
 Opts options_ {};
 SDL_Window *window_ {};
 SDL_Renderer *renderer_ {};
 };
}

In your application, you can instantiate an Window like so:

SDL::Window myWindow;

Avoid forward declarations whenever possible

Forward declarations should only be used if really necessary. Otherwise, they are just duplicating code, with the potential to make mistakes. If you are using a SDL_Window * in your header files, just make sure you #include <SDL.h> in it.

Avoid friend classes

Why is SDLContext a friend class of SDLWindow? If it needs to access some private member variables, maybe you should add public accessor functions instead? From the code you posted I don't see any reason why a friend class is necessary. In general, it is something to be avoided, since it bypasses the usual member protection scheme.

Use of std::experimental

While we all would like to have the latest and greatest language features available, consider that your code might be compiled by others using older compilers. Using experimental C++20 features might prevent them from using your code. Furthermore, observer_ptr<> is not doing anything really, it's just there as an annotation. It's there to convey to other users that this pointer is not owned. SO it is more useful to use this for the public API than for private member variables. But, that also brings me to:

Have classes own their own pointers to SDL2 objects

It looks a bit weird that SDLWindow has a private constructor that takes a pointer to an SDL_Window. Why not have SDLWindow call SDL_CreateWindow() itself in its constructor? The same goes for SDLRenderer.

When you do this, then SDLContext should no longer store pointers to SDL_Window and SDL_Renderer, but it should instead store a SDLWindow and SDLRenderer directly:

class Context {
 Window window_;
 Renderer renderer_;
public:
 Context(...);
 ...
};
answered Aug 11, 2019 at 15:21
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.