Search code examples
c++sdlsmart-pointerssdl-2unique-ptr

Is it possible to use SDL2 with smart pointers?


I have this line of code

//std::unique_ptr<SDL_Window> _window_; // this is somewhere else...
_window_ = std::make_unique<SDL_Window>(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _WIDTH_, _HEIGHT_, SDL_WINDOW_SHOWN));

it produces the following compiler error

In file included from /usr/include/c++/6/memory:81:0,
                 from /home/user/prj/src/main.cpp:4:
/usr/include/c++/6/bits/unique_ptr.h: In instantiation of ‘typename 
std::_MakeUniq<_Tp>::__single_object std::make_unique(_Args&& ...) [with _Tp = SDL_Window; _Args = {SDL_Window*}; typename 
std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<SDL_Window>]’:
/home/user/prj/src/main.cpp:36:170:   required from here
/usr/include/c++/6/bits/unique_ptr.h:791:30: error: invalid use of incomplete type ‘struct SDL_Window’
     { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }

why? (It works fine without smart pointers, so my guess is I didn't understand the syntax and this is trivial to fix. Will add source and CMakeLists.txt below.)

CMakeLists.txt

cmake_minimum_required(VERSION 3.7)
project(prj)

find_package(SDL2 REQUIRED)
include_directories(prj ${SDL2_INCLUDE_DIRS})

add_executable(prj main.cpp)
target_link_libraries(prj ${SDL2_LIBRARIES})

main.cpp

#include "SDL.h"
#include <memory>
#include <iostream>
#include <fstream>
#include <cstdint>

class Window
{

    public:

    Window()
        : _window_{nullptr}
        , _surface_{nullptr}
    {
        if(SDL_Init(SDL_INIT_VIDEO) < 0)
        {
            std::cerr << SDL_GetError() << std::endl;
        }
        else
        {
            _window_ = std::make_unique<SDL_Window>(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _WIDTH_, _HEIGHT_, SDL_WINDOW_SHOWN));

            if(_window_ == nullptr)
            {
                std::cerr << SDL_GetError() << std::endl;
            }
            else
            {
                _surface_ = std::make_unique<SDL_Surface>(SDL_GetWindowSurface(_window_.get()));
                SDL_FillRect(_surface_.get(), nullptr, SDL_MapRGB(_surface_->format, 0xFF, 0xFF, 0xFF));
                SDL_UpdateWindowSurface(_window_.get());
                SDL_Delay(1000);
            }
        }
    }

    ~Window()
    {
        SDL_DestroyWindow(_window_.get());
        SDL_Quit();
    }

    private:

    const int32_t _WIDTH_{600};
    const int32_t _HEIGHT_{400};

    std::unique_ptr<SDL_Window> _window_;
    std::unique_ptr<SDL_Surface> _surface_;

};


int main(int argc, char* argv[])
{
    Window window;
    return 0;
}

Solution

  • Solution

    Finally figured out the answer with a lot of trial and error, so will explain the solution here.

    This is the correct syntax:

    // first define the unique_ptr as member of class
    std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> _window_;
    
    // second, initialize in the member initialization list of class constructor
    // probably don't need to do this if not embedding as member of class
    class_name()
        : _window_(nullptr, SDL_DestroyWindow)
    {
        // blaa blaa SDL code etc
        _window_.reset(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN));
    }
    
    // finally we need to be able to delete
    // but this is handled automatically
    

    Explanation

    When we add the unique_ptr as a data member, we need to give both the type SDL_Window and the "deleter function format / syntax", becuase an ordinary delete call is not correct. We use decltype to automatically construct the correct deleter format from the deleter function. (Perhaps not the most accurate explanation.) In a way, decltype is somewhat like auto...

    std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> _window_;
    

    This object must be initialized. We do this in the constructor. We set the pointer to nullptr (because we do not want to initialize it before initializing SDL2) and we also set the deleter function.

    : _window_(nullptr, SDL_DestroyWindow)
    

    After initializing SDL, we then want to create a window. This is done most easily by calling the smart pointer reset() function. We pass it a new pointer returned by the function which creates the window.

    _window_.reset(SDL_CreateWindow(...));
    

    Done. Took a long time to figure out but makes sense now. References

    http://en.cppreference.com/w/cpp/memory/unique_ptr

    Why does my unique_ptr think is has a null function pointer deleter?

    What's wrong with this initialization of unique_ptr?