最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - How to hide dependency headers for a shared library - Stack Overflow

programmeradmin3浏览0评论

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
  • If it's only for implementation, can't you #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:42
  • 6 The pimpl pattern is excellent for this... why did you discard it? Remember you write an API for your users, and thus extra effort on your side must be taken to make their lives easier – Pepijn Kramer Commented Mar 17 at 7:44
  • Another option is to give your library an SDK with only abstract baseclasses and (one or more) factory functions. In the end some kind of dependency inversion is needed. – Pepijn Kramer Commented Mar 17 at 7:48
  • 2 It's up to you of course, but it seems like pimpl is the proper solution here. And I don't know what you mean by "it adds a TON of complexity and a bunch of other negatives". The complexity is rather minimal and the benefits are significant IMHO. – wohlstad Commented Mar 17 at 7:53
  • 1 You shouldn't even be mentioning glfwWindowShouldClose (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 a GLFWWindow* is probably enough). – molbdnilo Commented Mar 17 at 14:30
 |  Show 4 more comments

2 Answers 2

Reset to default 4

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

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.

发布评论

评论列表(0)

  1. 暂无评论