Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide dependency headers for a shared library

Tags:

c++

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.

like image 665
Caleb Burke Avatar asked Oct 15 '25 16:10

Caleb Burke


1 Answers

I 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:

  1. 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.

  2. 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 #included in the cpp which is required for destructing it as well as calling its methods).

  3. 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 the Window destructor, and probably delete the copy constructor and assignment).

  4. 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.

  5. 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).

like image 160
wohlstad Avatar answered Oct 18 '25 07:10

wohlstad



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!