Daniel Lee
Daniel Lee

Reputation: 199

How do I decouple C++ project header dependencies?

"Platform" is a C++ DLL project that includes SDL2. The project is configured with SDL2 includes and library directories. "Sample" is a console application that includes and links to "Platform", but makes no reference to SDL2 whatsoever; other than that implicitly made by my including a thing that includes that library. "Sample" fails to compile on a cpp file in "Platform" with the error that it cannot find SDL2's header files.

I can add the SDL2 include directory to the "Sample" project, but this creates a coupling that seems to defeat the reason I have separated "Platform" from "Sample" in the first place. I looked into precompiled headers, but that seems to solve a different problem; and I couldn't figure out how to get "Sample" to include the "Platform" PCH anyway.

error:

Severity    Code    Description Project File    Line    Suppression State
Error   C1083   Cannot open include file: 'SDL.h': No such file or directory    Sample  C:\Users\danie\Source\SingleScreen\Platform\display.h   6   

sample.cpp:

#include "platform.h"

int main(int argc, char** argv)
{

    Display_Configuration* display_configuration = new Display_Configuration();
    display_configuration->window_title = "Sandbox";
    display_configuration->dimension_x = 1280;
    display_configuration->dimension_y = 720;
    display_configuration->color_depth = 24;
    display_configuration->is_fullscreen = false;

    Platform* platform = new Platform(display_configuration);

    return 0;
}

platform.h:

#ifndef SINGLESCREEN_PLATFORM_PLATFORM_H
#define SINGLESCREEN_PLATFORM_PLATFORM_H

#ifdef  PLATFORM_EXPORTS 
#define PLATFORM_API __declspec(dllexport)  
#else
#define PLATFORM_API __declspec(dllimport)  
#endif

#include "display.h"
#include "controller.h"
#include "synthesizer.h"
#include "sampler.h"

extern PLATFORM_API class Platform
{
private:
    Display* _display;
    Controller* _controller;
    Synthesizer* _synthesizer;
    Sampler* _sampler;

public:
    Platform(Display_Configuration* display_configuration);
    ~Platform();
};

#endif //SINGLESCREEN_PLATFORM_PLATFORM_H

platform.cpp:

#include "pch.h"
#include "platform.h"

Platform::Platform(Display_Configuration* display_configuration)
{
    _display = new Display(display_configuration);
    _controller = new Controller();
    _synthesizer = new Synthesizer();
    _sampler = new Sampler();
}

Platform::~Platform()
{
    delete _display;
    delete _controller;
    delete _synthesizer;
    delete _sampler;
}

display.h:

#ifndef SINGLESCREEN_PLATFORM_DISPLAY_H
#define SINGLESCREEN_PLATFORM_DISPLAY_H

#include <vector>
#include <string>
#include "SDL.h"

struct Display_Configuration
{
    std::string window_title;
    bool is_fullscreen;
    int dimension_x;
    int dimension_y;
    int color_depth;
};

class Display
{
private:
    std::string _window_title;
    bool _is_fullscreen;
    int _dimension_x;
    int _dimension_y;
    int _color_depth;
    SDL_Window* _window;
    SDL_Surface* _surface;

public:
    Display(Display_Configuration* display_configuration);
    ~Display();
    bool initialize();

};

#endif //SINGLESCREEN_PLATFORM_DISPLAY_H

display.cpp:

#include "pch.h"
#include <iostream>
#include "display.h"


Display::Display(Display_Configuration* display_configuration)
{
    _window_title = display_configuration->window_title;
    _is_fullscreen = display_configuration->is_fullscreen;
    _dimension_x = display_configuration->dimension_x;
    _dimension_y = display_configuration->dimension_y;
    _color_depth = display_configuration->color_depth;
}

Display::~Display()
{
}

bool Display::initialize()
{
    _window = NULL;
    _surface = NULL;

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cout << "Failed to initialize the video subsystem. The error was:" << std::endl << SDL_GetError() << std::endl;
        return false;
    }
    
    _window = SDL_CreateWindow(
        _window_title.c_str(),
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        _dimension_x, _dimension_y,
        SDL_WINDOW_SHOWN | ( _is_fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : NULL )
    );

    if (_window == NULL) {
        std::cout << "Failed to create a rendering window. The error was:" << std::endl << SDL_GetError() << std:: endl;
        return false;
    }

    _surface = SDL_GetWindowSurface(_window);

    if (_surface == NULL) {
        std::cout << "Failed to create a rendering surface. The error was:" << std::endl << SDL_GetError() << std::endl;
        return false;
    }

    return true;
}

Upvotes: 2

Views: 748

Answers (1)

Julian Kirsch
Julian Kirsch

Reputation: 754

TL;DR:

  • move the include from the header into the source
  • forward declare SDL_Window and SDL_Surface

Judging by your profile, you recently decided to learn programming. Don't worry, that's cool and you'll figure it out.

Why do I think so? It took me a moment to understand your question because [Y]ou seem at a loss for the right words.

What you are asking for is how to hide implementation details properly in C++. As far as I understood, you're working on a library using SDL under the hood. And you wish to use this library in applications, that do not use SDL directly.

Don't worry, you can achieve that. You just need to fix two things:

  • Remove the line #include "SDL.h" from any public header and put the line into your cpp-files, that require it. Headers are considered public, if they're meant to be used by another target, like your sample.
  • Forward declare SDL's types.

Forward wat?

Compilers are of such nature, that they want to know everything. If they don't get that, you'll get slapped with compilation errors.

When it comes to types such as SDL_Window, the compiler wants to know:

  • Does it exist?
  • How big is the type?
  • What attributes does it have?
  • Which methods does it provide?

Luckily, we can tell the nosy compiler to mind its own business by using so called "forward declarations". I forward declaration looks like this:

// Example for your Display class.
class Display;

// Example for SDL
struct SDL_Window;
struct SDL_Surface;

Using forward declarations we make a promise, that the types Display, SDL_Window and SDL_Surface exist, without including their headers, and they'll be defined somewhere else.

This allows us to store pointers (or references) to instances of those types. This way the include #include "SDL.h" can be moved from the header display.h to the source display.cpp. Your sample wouldn't need to know about SDL's whereabouts.

IMPORTANT! Types that were forward declared cannot be used without definition.

Let's look say you forward declared your own class display in a file; i.e. like this:

class display;

int main() {
    auto size = sizeof(display); // Nope!
    auto ptr = new display{};    // Nope!
    ptr->initialize();           // Nope!

    display object_from_somewhere_else;
    display* ptr2 = &object_from_somewhere_else; // Yes!

    return 0;
};

In order to make a type useable again, we need to include the header that defines the type.

class display;
#include "display.h"

or

#include "display.h"
class display;

int main() {
    auto size = sizeof(display); // Yes!
    auto ptr = new display{};    // Yes!
    ptr->initialize();           // Yes!

    display object_from_somewhere_else;
    display* ptr2 = &object_from_somewhere_else; // Yes!

    return 0;
};

I know this might be a lot to for once. Just bear with me one more moment and let's take a look at how the final result would look like:

display.h

#ifndef SINGLESCREEN_PLATFORM_DISPLAY_H
#define SINGLESCREEN_PLATFORM_DISPLAY_H

#include <vector>
#include <string>

struct SDL_Window;
struct SDL_Surface;

struct Display_Configuration
{
    std::string window_title;
    bool is_fullscreen;
    int dimension_x;
    int dimension_y;
    int color_depth;
};

class Display
{
private:
    std::string _window_title;
    bool _is_fullscreen;
    int _dimension_x;
    int _dimension_y;
    int _color_depth;
    SDL_Window* _window;
    SDL_Surface* _surface;

public:
    Display(Display_Configuration* display_configuration);
    ~Display();
    bool initialize();

};

#endif //SINGLESCREEN_PLATFORM_DISPLAY_H

display.cpp

#include "pch.h"
#include <iostream>
#include "display.h"

#include "SDL.h"

Display::Display(Display_Configuration* display_configuration)
{
    _window_title = display_configuration->window_title;
    _is_fullscreen = display_configuration->is_fullscreen;
    _dimension_x = display_configuration->dimension_x;
    _dimension_y = display_configuration->dimension_y;
    _color_depth = display_configuration->color_depth;
}

Display::~Display()
{
}

bool Display::initialize()
{
    _window = NULL;
    _surface = NULL;

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cout << "Failed to initialize the video subsystem. The error was:" << std::endl << SDL_GetError() << std::endl;
        return false;
    }
    
    _window = SDL_CreateWindow(
        _window_title.c_str(),
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        _dimension_x, _dimension_y,
        SDL_WINDOW_SHOWN | ( _is_fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : NULL )
    );

    if (_window == NULL) {
        std::cout << "Failed to create a rendering window. The error was:" << std::endl << SDL_GetError() << std:: endl;
        return false;
    }

    _surface = SDL_GetWindowSurface(_window);

    if (_surface == NULL) {
        std::cout << "Failed to create a rendering surface. The error was:" << std::endl << SDL_GetError() << std::endl;
        return false;
    }

    return true;
}

Upvotes: 1

Related Questions