C++ Keyboard Controller I've written a Controller for the keyboard so that I can map certain keys to callbacks within a game. A key can only call one callback, but a callback can be called by many keys.

Currently, a Controller has two std::maps - one for when the key(s) is pressed/held, and one for when the key(s) are released. These maps are SDL_Scancode to std::string (Which I may change later to an enum rather than a string? But strings are easier to read so maybe not.) The Controller also has a ControllerContext object, which is just a wrapper around two std::maps of std::string to std::function&lt;void(void)&gt;: one map for callbacks to happen when a key is pressed, another for callbacks to happen when a key is released. 

I feel like some kind of change could be made to make the code more readable, and I am unsure of if I should have the context stored in a shared_ptr, or if I should just keep a raw pointer within the Controller. 

This is how it would work:

//An event union provided by SDL
//https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlevent.html
SDL_Event e;
//A controller
Controller controller_;
ControllerContext context_;

//Build the context
controller_.SetContext(&amp;context);

//Poll all events in the event queue, feeding them to the structure e
while(SDL_PollEvent(&amp;e))
{
   switch(e.type)
       case SDL_KEYDOWN:
           controller_.OnKeyPress(e.key);
           break;
       case SDL_KEYUP:
           controller_.OnKeyRelease(e.key);
           break;
       default: 
           break;
}


keyboardcontroller.h

#ifndef SHMUPPY_HEADER_KEYBOARDCONTROLLER_H_
#define SHMUPPY_HEADER_KEYBOARDCONTROLLER_H_

#include &lt;map&gt;
#include &lt;SDL.h&gt;
#include &lt;iostream&gt;
#include &lt;exception&gt;
#include &lt;string&gt;
#include &lt;memory&gt;


struct ControllerContext;

class Controller
{
public:
    Controller();

    int OnKeyPress(const SDL_Event &amp;e);

    int OnKeyRelease(const SDL_Event &amp;e);

    void SetContext(std::shared_ptr&lt;ControllerContext&gt; context_);

protected:
    std::map&lt;SDL_Scancode, std::string&gt; keypress_action_map_;
    std::map&lt;SDL_Scancode, std::string&gt; keyrelease_action_map_;
    std::shared_ptr&lt;ControllerContext&gt; controller_context_;
};


#endif


keyboardcontroller.cpp

#include "KeyboardController.h"
#include "ControllerContext.h"


Controller::Controller()
{
    //Some test initializations
    keypress_action_map_[SDL_SCANCODE_LEFT] = "Left";
    keypress_action_map_[SDL_SCANCODE_DOWN] = "Down";
    keypress_action_map_[SDL_SCANCODE_RIGHT] = "Right";
    keypress_action_map_[SDL_SCANCODE_UP] = "Up";


    keyrelease_action_map_[SDL_SCANCODE_LEFT] = "Left";
    keyrelease_action_map_[SDL_SCANCODE_DOWN] = "Down";
    keyrelease_action_map_[SDL_SCANCODE_RIGHT] = "Right";
    keyrelease_action_map_[SDL_SCANCODE_UP] = "Up";
}

int Controller::OnKeyPress(const SDL_Event&amp; e)
{
    try
    {
        //std::cout &lt;&lt; keypress_action_map_.at(e.key.keysym.scancode) &lt;&lt; std::endl;
        controller_context_-&gt;keypress_events_.at(keypress_action_map_.at(e.key.keysym.scancode))();
        return 1;
    }
    catch (std::exception ex)
    {
        return 0;
    }
}

int Controller::OnKeyRelease(const SDL_Event&amp; e)
{
    try
    {
        std::cout &lt;&lt; keyrelease_action_map_.at(e.key.keysym.scancode) &lt;&lt; std::endl;
        return 1;
    }
    catch (std::exception ex)
    {
        return 0;
    }
}


void Controller::SetContext(std::shared_ptr&lt;ControllerContext&gt; context_)
{
    controller_context_ = context_;
}


ControllerContext.h

#ifndef SHMUPPY_HEADER_CONTROLLER_CONTEXT_H
#define SHMUPPY_HEADER_CONTROLLER_CONTEXT_H


#include &lt;map&gt;
#include &lt;functional&gt;

struct ControllerContext
{
    std::map&lt;std::string, std::function&lt;void(void)&gt;&gt; keypress_events_;
    std::map&lt;std::string, std::function&lt;void(void)&gt;&gt; keyrelease_events_;
};


#endif
Edit: Tested implementations below.

As jason says, returning either 0 or 1, looks like boolean. Change int to bool and return true or false.

Drop the std::string layer. It doesn't add anything over enum. And enum are just as readable and there is no way typos can appear with enums.

I don't have any experience with SDL myself, but I've written a keyboard -> action controller in an attempt at a game.

I'd also like to drop the std::map in favor of a std::vector. The trick is to use continuous enums and use those as indices for the std::vector.

Something like this:

enum class KeyboardEvents : byte
{
    ON_RELEASE,
    ON_PRESS,

    ORDINAL
};

typedef std::function&lt;void(void)&gt; keyboardFunc_t;

std::vector&lt;keyboardFunc_t&gt; keyboardEvents(KeyboardEvents::ORDINAL);

void bindControls()
{
    auto onReleaseLambda = [&amp;]()
    {
        // Do stuff
    };
    auto onPressLambda = [&amp;]()
    {
        // Do stuff
    };

    // Bind with lambdas
    keyboardEvents[KeyboardEvents::ON_RELEASE] = onReleaseLambda;
    keyboardEvents[KeyboardEvents::ON_PRESS] = onPressLambda;

    // Alternatively, use std::bind
    keyboardEvents[KeyboardEvents::ON_RELEASE] = std::bind(&amp;ClassName::onRelease, classInstancePtr);
    keyboardEvents[KeyboardEvents::ON_PRESS] = std::bind(&amp;ClassName::onPress, classInstancePtr);
}


I just wrote this on the top off my head. I'm not sure if it'll compile, but you get the idea.

I would also prefer lambdas in favor of std::bind.

As for storing std::shared_ptr&lt;ControllerContext&gt; controller_context_; in a raw or shared_ptr, I'd say neither. But DO use std::unique_ptr. Using a unique_ptr will make sure your controller class is not default copyable, which is a good thing when dealing with limited resources.

Like this std::unique_ptr&lt;ControllerContext&gt; controller_context_;

Edit: From here and on:

Note, don't look too closely on main.cpp, it was mainly copied from somewhere to get SDL2 running.

ControllerContext.h:

#ifndef SHMUPPY_HEADER_CONTROLLER_CONTEXT_H
#define SHMUPPY_HEADER_CONTROLLER_CONTEXT_H


#include &lt;vector&gt;
#include &lt;functional&gt;

struct ControllerContext
{

    ControllerContext()
        : keyPressEvents(SDL_NUM_SCANCODES)
        , keyReleaseEvents(SDL_NUM_SCANCODES)
    {
        keyPressEvents.resize(SDL_NUM_SCANCODES);
        keyReleaseEvents.resize(SDL_NUM_SCANCODES);
    }

    typedef std::function&lt;void(void)&gt;   KeyboardFunc_t;
    typedef std::vector&lt;KeyboardFunc_t&gt; KeyEvents;

    KeyEvents   keyPressEvents;
    KeyEvents   keyReleaseEvents;
};


typedef std::unique_ptr&lt;ControllerContext&gt;  ControllerContext_ptr;

#endif


keyboardcontroller.h:

#ifndef SHMUPPY_HEADER_KEYBOARDCONTROLLER_H_
#define SHMUPPY_HEADER_KEYBOARDCONTROLLER_H_

#include &lt;map&gt;
#include &lt;SDL.h&gt;
#include &lt;iostream&gt;
#include &lt;exception&gt;
#include &lt;string&gt;
#include &lt;memory&gt;


#include "ControllerContext.h"



class Controller
{
public:
    Controller();

    bool OnKeyPress(const SDL_Event &amp;e);

    bool OnKeyRelease(const SDL_Event &amp;e);

    // Clear by setting empty func
    void SetReleaseCallback(SDL_Scancode code, const ControllerContext::KeyboardFunc_t &amp; func);
    void SetPressCallback(SDL_Scancode code, const ControllerContext::KeyboardFunc_t &amp; func);

    // Clear both press and release
    void ClearCallback(SDL_Scancode code);

protected:
    ControllerContext_ptr controller_context_;
};


#endif


keyboardcontroller.cpp:

#include "KeyboardController.h"
#include "ControllerContext.h"


Controller::Controller()
    : controller_context_(new ControllerContext())
{
    //Some test initializations
    auto leftPress = [&amp;]()
    {
        // Left press lambda
        int debug=0;
    };
    auto leftRelease = [&amp;]()
    {
        // Left release lambda
        int debug=0;
    };

    SetPressCallback(SDL_SCANCODE_LEFT, leftPress);
    SetReleaseCallback(SDL_SCANCODE_LEFT, leftRelease);
}

bool Controller::OnKeyPress(const SDL_Event &amp;e)
{
    const auto &amp; scancode = e.key.keysym.scancode;

    const auto &amp; pressFunc = controller_context_-&gt;keyPressEvents[scancode];

    if (pressFunc)
    {
        // Call callback if valid
        pressFunc();
        return true;
    }

    return false;
}

bool Controller::OnKeyRelease(const SDL_Event &amp;e)
{
    const auto &amp; scancode = e.key.keysym.scancode;

    const auto &amp; pressFunc = controller_context_-&gt;keyReleaseEvents[scancode];

    if (pressFunc)
    {
        // Call callback if valid
        pressFunc();

        return true;
    }

    return false;
}

void Controller::SetReleaseCallback(SDL_Scancode code, const ControllerContext::KeyboardFunc_t &amp; func)
{
    controller_context_-&gt;keyReleaseEvents[code] = func; 
}

void Controller::SetPressCallback(SDL_Scancode code, const ControllerContext::KeyboardFunc_t &amp; func)
{
    controller_context_-&gt;keyPressEvents[code] = func;
}

void Controller::ClearCallback(SDL_Scancode code)
{
    controller_context_-&gt;keyPressEvents[code] = ControllerContext::KeyboardFunc_t();
    controller_context_-&gt;keyReleaseEvents[code] = ControllerContext::KeyboardFunc_t();
}


main.cpp:

#include &lt;SDL.h&gt;
#include &lt;iostream&gt;

class TestMain
{
    public:
        void handlePressW()
        {
            // W press
            int debug=0;
        }
        void handleReleaseW()
        {
            // W release
            int debug=0;
        }
};

#include "keyboardcontroller.h"

int _stdcall WinMain(struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance, char *lpszCmdLine, int nCmdShow)
{
    if (SDL_Init(SDL_INIT_EVERYTHING) &lt; 0)
    {
        /* Handle problem */
        fprintf(stderr, "%s\n", SDL_GetError());
    }
    SDL_Window* window = SDL_CreateWindow("Window caption", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 852, 480, 0);
    if (window == NULL)
    {
        /* Handle problem */
        fprintf(stderr, "%s\n", SDL_GetError());
        SDL_Quit();
    }
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    if (renderer == NULL)
    {
        /* Handle problem */
        fprintf(stderr, "%s\n", SDL_GetError());
        SDL_Quit();
    }


    Controller keycontroller;

    // Bind testmain methods
    TestMain testmain;
    keycontroller.SetPressCallback(SDL_SCANCODE_W, std::bind(&amp;TestMain::handlePressW, testmain));
    keycontroller.SetReleaseCallback(SDL_SCANCODE_W, std::bind(&amp;TestMain::handleReleaseW, testmain));


    bool running = true;
    while (running)
    {
        /* Clear the buffer of color, setting it to black */
        SDL_RenderClear(renderer);

        /* Draw the buffer into the window */
        SDL_RenderPresent(renderer);

        /* Handle input and events: */
        SDL_Event sdl_event;
        while (SDL_PollEvent(&amp;sdl_event) &gt; 0)
        {
            switch (sdl_event.type)
            {
                case(SDL_KEYDOWN):
                {
                    keycontroller.OnKeyPress(sdl_event);
                    break;
                }

                case(SDL_KEYUP):
                {
                    keycontroller.OnKeyRelease(sdl_event);
                    break;
                }


                case(SDL_QUIT):
                    running = false;
                    break;
            }

        }

        /* Run your logic. For example: */
    }

    SDL_Quit();

    return 0;
}