Проблемы с интерфейсом и реализацией?

В настоящее время в игре, над которой я работаю, у меня есть класс окна:

class UserWindow{
    *interface here*
};

Я приложил большие усилия, чтобы не раскрывать детали реализации этого окна; в частности, тот факт, что он использует GLFW под капотом. Я не хочу раскрывать какой-либо GLFW клиенту в ситуации, когда я в конечном итоге удалю/заменю его позже. Все хорошо, пока я, наконец, не добрался до обработки клавиш для окна. Видите ли, GLFW использует макросы для идентификации ключей, как показано здесь:

auto GLFWKeyPressCallback(GLFWwindow* wind, int key, int, int, int) -> void {
    if(key == GLFW_KEY_???)
        *do stuff*
} 

Как известно большинству программистов на C++, макросы токсичны, и их следует избегать. Проблема здесь в том, что я предоставляю пользователю интерфейс UserWindowEventListener для подключения и получения событий. Вопрос в том, как я могу раскрыть идентификаторы для этих ключей GLFW, не раскрывая весь API GLFW? Поскольку GLFW использует вышеупомянутые (злые) макросы для их идентификации, они будут просачиваться в глобальное пространство имен, что разрушает идею наличия «внутреннего» пространства имен. Единственным другим решением, о котором я подумал, было иметь простую старую структуру, содержащую статические целые числа для каждого ключа, и назначать их в файле .cpp, например так:

.hpp:

struct UserWindowKey{
    static const int up, down, left, right... (repeat ad nauseam)   
};

.cpp:

const int UserWindowKey::up = GLFW_KEY_UP...

Однако это кажется утомительным, неуправляемым и, как правило, вообще похоже на плохую практику. Любые идеи?

изменить: некоторые детали реализации в псевдокоде, чтобы устранить любые неясности:

Окно пользователя:

//constructor
UserWindow(){
    //key_func just fires the signal that's in the event handler below.
    glfwSetKeyCallback(window_handle, key_func);
}

Слушатель UserWindowEventListener:

struct UserWindowEventListener{
    key_signal_type key_signal;
};

использование:

auto main() -> int {
    UserWindow wind;
    wind.event_listener.key_signal.connect(*MY_FUNC*);
    wind.event_listener.poll();
}

person Shokwav    schedule 04.07.2014    source источник
comment
GLFW_KEY_??? кажутся просто #defined константами манифеста, а не макросами.   -  person paisanco    schedule 04.07.2014
comment
@bogeyc: извините, я называю все, что использует макрос #define. проблема все еще остается в силе.   -  person Shokwav    schedule 04.07.2014
comment
Чтобы лучше понять вашу ситуацию, предоставляет ли пользователь вашего фреймворка метод обратного вызова, который вы затем передаете в GLFW? например, SetCallback(GLFWKeyPressCallback) или что-то в этом роде?   -  person Micah Zoltu    schedule 04.07.2014
comment
@MicahCaldwell: я добавил некоторые подробности в свой пост, чтобы прояснить любые неясности. Короче говоря, у меня есть частная статическая функция-член для каждого обратного вызова glfw, единственной задачей которой является удаление любых деталей, относящихся к glfw, из обратного вызова (например, аргумента GLFWwindow*) и запуск сигнала события в UserWindowEventListener.   -  person Shokwav    schedule 04.07.2014
comment
@Shokwav, да, принцип избегания #define остается в силе, но здесь вы имеете дело со сторонней библиотекой. Поскольку многие из вас в какой-то момент хотят заменить GLFW, возможно, иерархия абстрактных базовых классов с классами реализации на основе GLFW будет одним из подходов, позже вы сможете реализовать какую-то другую библиотеку.   -  person paisanco    schedule 04.07.2014
comment
@bogeyc: в основном это то, что я делаю, но у меня проблемы с абстрагированием макросов, которые использует GLFW. Я не могу раскрыть макросы, не раскрывая весь API GLFW, так как вся библиотека находится в одном заголовке.   -  person Shokwav    schedule 04.07.2014
comment
В ваших модификациях, когда пользователь вызывает wind.event_listener.key_signal.connect, он принимает функцию в качестве параметра. Я предполагаю, что эта функция создана и предоставлена ​​пользователем. Я также предполагаю, что вы хотите, чтобы функция вызывалась, когда GLFW запускает событие нажатия клавиши? Кроме того, правильно ли я понимаю, что подпись этой функции (MY_FUNC) должна содержать информацию о том, какая клавиша была нажата, чтобы пользователь мог включить ее, но вы хотите, чтобы они могли сделать это, не зная ничего о GLFW?   -  person Micah Zoltu    schedule 04.07.2014
comment
@MicahCaldwell Это правильно.   -  person Shokwav    schedule 04.07.2014
comment
Однако это кажется утомительным, неуправляемым и, как правило, вообще похоже на плохую практику. - лучшее, что вы получите... если это слишком утомительно, пусть скрипт sh/perl/ruby/python сгенерирует его, используя последовательное преобразование (например, отрывая GLFW_KEY_ и конвертируя в нижний регистр).   -  person Tony Delroy    schedule 04.07.2014


Ответы (1)


Я бы создал промежуточный метод, который преобразует ключи GLFW в ваши собственные ключи, определенные таким образом, который вам удобен и кажется нормальным. Лично я бы пошел с таким перечислением:

enum MyKeys
{
    UP,
    DOWN,
    LEFT,
    RIGHT,
    // the rest
}

Затем пользователь предоставит вам обратный вызов, а вы предоставите GLFW другой обратный вызов. Когда GLFW вызывает ваш обратный вызов, вы конвертируете ключ в общедоступное перечисление, а затем запускаете обратный вызов, предоставленный пользователем.

Псевдокод для вашего обратного вызова может выглядеть примерно так:

auto GLFWKeyPressCallback(GLFWwindow* wind, int glfwKey, int a, int b, int c) -> void
{
    auto myKey = ConvertGlfwKeyToMyKey(glfwKey);
    user_callback(myKey, a, b, c);
}

подпись user_callback будет выглядеть примерно так:

void user_callback(MyKeys, int, int, int);

ConvertGlfwKeyToMyKey может выглядеть примерно так:

MyKeys ConvertGlfwKeyToMyKey(glfwKey)
{
    switch (glfwKey)
    {
    case GLFW_KEY_UP:
        return MyKeys.UP;
    case GLFW_KEY_DOWN:
        return MyKeys.DOWN;
    // the rest
    }
}

Очевидным недостатком этой системы является то, что вам придется пройти и заполнить функцию Convert. Преимущество заключается в том, что вы абстрагируете весь материал GLFW от пользователя без необходимости иметь какие-либо макросы или загрязнять свой код кучей структур.

В качестве альтернативы вы можете выбрать какой-либо другой механизм для сопоставления ключей GLFW с MyKeys. Если ключи GLFW охватывают все значения в диапазоне, вы можете упорядочить свое перечисление в том же порядке, а затем просто сместить и привести тип от GLFW int к вашему перечислению.

person Micah Zoltu    schedule 04.07.2014