I'm writing a super basic game engine, using GLFW for window creation.
To keep this post brief, assume this engine just creates and destroys a window.
This class Window
is defined in window.hpp
, implemented in window.cpp
and compiled to a shared library libwindow.o
.
// window.hpp
#pragma once
#include <GLFW/glfw3.h>
class Window {
...
};
Currently if a user wanted to use Window
, they include window.hpp
but in doing this they also include GLFW. But GLFW is already statically linked to libwindow.o
, I dont want uses to have to download the GLFW headers.
Currently my solution has been to avoid including GLFW all together and let the linker figure it out;
// window.hpp
#pragma once
// #include <GLFW/glfw3.h>
// forward declare whats used in header file
typedef struct GLFWwindow GLFWwindow;
int glfwWindowShouldClose(GLFWwindow* handle);
class Window {
...
};
IMO this solutions is hacky.
Is there's a better way?
PS: I've already considered and avoided the PImpl idiom.
I'm writing a super basic game engine, using GLFW for window creation.
To keep this post brief, assume this engine just creates and destroys a window.
This class Window
is defined in window.hpp
, implemented in window.cpp
and compiled to a shared library libwindow.o
.
// window.hpp
#pragma once
#include <GLFW/glfw3.h>
class Window {
...
};
Currently if a user wanted to use Window
, they include window.hpp
but in doing this they also include GLFW. But GLFW is already statically linked to libwindow.o
, I dont want uses to have to download the GLFW headers.
Currently my solution has been to avoid including GLFW all together and let the linker figure it out;
// window.hpp
#pragma once
// #include <GLFW/glfw3.h>
// forward declare whats used in header file
typedef struct GLFWwindow GLFWwindow;
int glfwWindowShouldClose(GLFWwindow* handle);
class Window {
...
};
IMO this solutions is hacky.
Is there's a better way?
PS: I've already considered and avoided the PImpl idiom.
Share Improve this question edited Mar 17 at 8:38 wohlstad 30k17 gold badges61 silver badges93 bronze badges asked Mar 17 at 7:33 Caleb BurkeCaleb Burke 655 bronze badges 9 | Show 4 more comments2 Answers
Reset to default 4I think that the PImpl idiom is actually very suited for this problem, despite your initial tendency to avoid it (see note (4) below regarding Java).
It enables to better separate the interface from the implementation, and allows the implementation to use headers and types that need not be exposed to the user of the interface.
Here is a minimal demo of appying the PImpl idiom in your case:
Window.h:
#include <memory> // for std::unique_ptr; if you want to avoid it you can use a raw pointer
class WindowImpl; // forward declaration
class Window {
public:
Window();
~Window();
int SomeMethod(int x);
private:
std::unique_ptr<WindowImpl> m_impl;
};
Window.cpp:
#include "Window.h"
#include "WindowImpl.h"
#include <assert.h>
Window::Window() {
m_impl = std::make_unique<WindowImpl>();
}
Window::~Window() = default; // must be in cpp
int Window::SomeMethod(int x) {
assert(m_impl);
return m_impl->SomeMethod(x);
}
WindowImpl.h:
#include <GLFW/glfw3.h>
class WindowImpl {
public:
WindowImpl();
int SomeMethod(int x);
private:
GLFWwindow m_win;
};
WindowImpl.cpp:
WindowImpl::WindowImpl() {
// ...
}
int WindowImpl::SomeMethod(int x) {
// ...
return 0;
}
Notes:
The client that uses
#include "Window.h"
does not need to know<GLFW/glfw3.h>
or other internally used headers - which is the main benefit of the idiom.The implementation of all the methods of
Window
must be in the cpp, including constructor and destructor, to allow to keep the h file as clean as possible (WindowImpl
is forward-declared in the h file, and only fully defined when#include
d in the cpp which is required for destructing it as well as calling its methods).If you have problem exposing the usage of
std::unique_ptr
in your public header, you can use a raw pointer instead (but you must remember to deallocate it in theWindow
destructor, and probably delete the copy constructor and assignment).You mentioned that you wanted to avoid the idiom because of your aversion to Java. I assume (as @Botje commented) that you refer to the tendency in Java to use interfaces abundantly. In C++ it is not the case in general, but in certain cases (like creating a clean library interface) it is a good solution.
You might want to have a header only
WindowImpl
, i.e. implement all the methods inline. It might be more elegant for certain cases (although this is very much opinion based).
PIMPL is simple in C++, but you need to provide the deleter, in case unique_ptr
is used:
// window.hpp
#include <memory>
//forward declare the class:
struct GLFWwindow;
class Window{
public:
Window(): Window{ (GLFWwindow*)nullptr } {};
Window(Window& parent)
: Window{parent.m_glf_wnd_ptr.get()}
{};
private:
Window(GLFWwindow*); //delegated constructor
// declare a deleter:
struct pimpl_deleter{
void operator()(GLFWwindow*) const;
};
std::unique_ptr<GLFWwindow, pimpl_deleter> m_glf_wnd_ptr;
};
Now you need the implementation to define the deleter as well:
#include <GLFW/glfw3.h>
#include "window.hpp"
Window::Window(GLFWwindow* g_parent)
: m_glf_wnd_ptr
{ glfwCreateWindow
( OTHER_ARGUMENTS
, g_parent); }
{/*BLAH BLAH*/};
//Add the deleter logic:
void Window::pimpl_deleter::operator()(GLFWwindow* wnd) const
{ glfwDestroyWindow(wnd); };
Providing the deleter definition in the same TU as the owner of PIMPL is essential for correct compilation of code. std::default_deleter
always complains about incomplete types(GLFWwindow
here) at compile-time. This signals the essence of correct disposal of dynamic object.
#include <GLFW/glfw3.h>
in the cpp file rather than h ? And why do you want avoid the pimpl idiom which is classic for separation of interface from implementation ? – wohlstad Commented Mar 17 at 7:42glfwWindowShouldClose
(or anything else belonging to glfw) in the header. Once you move all implementation code out of the header, you might notice that your pimpl can be very simple (just a struct that holds aGLFWWindow*
is probably enough). – molbdnilo Commented Mar 17 at 14:30