Предоставление дочерних методов объекта без дублирования - PullRequest
0 голосов
/ 27 января 2012

У меня есть объект класса Level, который имеет несколько подсистем (дочерних объектов), которые он использует для управления своим состоянием (например, EntityManager, InputManager, Physics).Я хочу раскрыть некоторые методы этих подсистем во внешнем интерфейсе Level.

. Вот одно из решений:

uint32_t Level::CreateEntity()
{
    return entityManager.CreateEntity();
}

Entity& Level::GetEntity(uint32_t entityId)
{
    return entityManager.GetEntity(entityId);
}

uint16_t Level::CreateInputState()
{
    return inputManager.CreateInputState();
}

void Level::AttachInputState(uint32_t entityId, uint16_t inputStateId)
{
    inputManager.AttachInputState(entityId, inputStateId);
}

InputState& Level::GetInputState(uint16_t inputStateId)
{
    return inputManager.GetInputState(inputStateId);
}

Это решение требует, чтобы я продублировал объявления методов внутрикласс Level и запись однострочных вызовов, которые перенаправляют элемент управления в подсистему.В проектах, над которыми я работал в прошлом, управлять им было проблематично.

Другое решение заключается в предоставлении подсистем в открытом интерфейсе.Предпочтительно этого можно избежать, поскольку это не важно для объектов за пределами Level, в подсистему которых перенаправляются вызовы.

Существует ли проект, который мог бы более элегантно решить эту проблему?

Ответы [ 3 ]

1 голос
/ 28 января 2012

По запросу ОП я опубликую решение, которое я предложил в комментариях. Я совершенно уверен, что в новом стандарте C ++ есть лучший шаблон с шаблонами, но в любом случае я опубликую решение препроцессора, которое уродливо и не должно использоваться!

#define FUNCTION_DECLARATION(RETURNTYPE, FUNCTIONNAME, ...) \
     RETURNTYPE FUNCTIONNAME(__VA_ARGS__)

Это будет использоваться в объявлении класса как:

class Level {
    FUNCTION_DECLARATION(uint32_t, CreateEntity);
    FUNCTION_DECLARATION(Entity&, GetEntity, uint32_t);
};

Определение будет выглядеть так:

#define FUNCTION_DEFINITION(RETURNTYPE, PROPERTY, FUNCTIONNAME, ...) \
    RETURNTYPE Level::FUNCTIONNAME(__VA_ARGS__) \
    { \
        return PROPERTY.FUNCTIONNAME(__VA_ARGS__); \
    } \

А теперь очень уродливое использование, чтобы сделать эту работу:

FUNCTION_DEFINITION(Entity&, entityManager, GetEntity, uint32_t(entityId))

Я не могу гарантировать, что это будет работать для любого типа, также я не тестировал большую часть кода. Как вы можете видеть, этот «взлом» с помощью ввода будет работать только для простых типов, а не для ссылок или указателей. На классах это вызовет конструктор копирования!

Конечно, это можно улучшить, заменив VA_ARGS переменной для типа и именем переменной для каждого аргумента функции, но, опять же, это очень утомительно, и для каждого числа аргументов нужно написать шаблон, который должен быть используемый. В результате вы получите почти столько же кода, сколько раньше.

Итак, позвольте мне еще раз заявить: придерживайтесь шаблонов, они намного лучше, чем это! Просто я не знаю, как с ними это сделать. Поэтому, пожалуйста, если есть кто-нибудь, кто знает, как сделать это с помощью шаблонов, вместо того, чтобы избивать меня этим ** кодом, напишите прекрасное решение, которого все здесь ждут.

1 голос
/ 28 января 2012

Другой способ агрегирования функций - это частное наследование. Затем вы можете превратить некоторые унаследованные частные методы в открытые методы с помощью директивы using. Например:

#include <iostream>

class feature_A
{
public:
  void func_A1() { std::cout << "A1" << std::endl; }
  void func_A2() { std::cout << "A2" << std::endl; }
};

class feature_B
{
public:
  void func_B1() { std::cout << "B1" << std::endl; }
  void func_B2() { std::cout << "B2" << std::endl; }
};

class compound : private feature_A, private feature_B
{
public:
  // Provide these functions as-is.
  using feature_A::func_A1;
  using feature_B::func_B1;

  // Combine these two functions.
  void func_C2() { func_A2(); func_B2(); }
};

int main()
{
  compound c;
  c.func_A1();
  c.func_B1();
  c.func_C2();
  // c.func_A2();   // error: ‘void feature_A::func_A2()’ is inaccessible
}

Одним из ограничений этих using объявлений является то, что они имеют имя. Если у вас есть несколько перегрузок одной и той же функции, вы не можете выбрать только одну, которая будет опубликована. Аналогично для шаблонного метода: вы не можете использовать using, чтобы выбрать только одну специализацию.

0 голосов
/ 27 января 2012

Единственное, что я знаю об этом, это сделало бы интерфейс более элегантным, - это чтобы другой объект содержал private только EntityManager, Physics и т. Д., И чтобы он public имел доступ к функциям, которые вызываюткаждая из функций, которую вы хотите, чтобы клиенты могли вызывать на базовых объектах.Сделайте так, чтобы Level имел экземпляр этих прокси-объектов в общедоступном интерфейсе, и опционально сделайте эти прокси не подлежащими копированию, перемещению и т. Д.

Это не уменьшит объем работы для вас (это будетфактически увеличит его), но это сделает интерфейс более организованным и более простым в использовании.

Пример:

class Physics {
public:
    T gravity() { ... } // we want them to be able to call this

    T2 nope() { ... }   // but not this
};

class LevelPhysics {
public:
    T gravity() { return phys.gravity(); }

private:
    LevelPhysics(Physics& phys) : phys(phys) { }

    Physics& phys;

    friend class Level;
};

class Level {
public:
    LevelPhysics GetPhysics() { return LevelPhysics(phys); }

private:
    Physics phys;
};

Тогда вы можете использовать его как

const LevelPhysics& phys = lvl.GetPhysics();

phys.gravity();
// but you can't use Physics::nope

К сожалению, нет языковой функции "сделать это для вас".Вы не можете обойтись без некоторого кодирования самостоятельно.

В качестве альтернативы, если вы знаете все классы, которым требуется доступ к секретным членам Physics, вы можете просто сделать это:

class Physics {
public:
    T gravity() { ... }

private:
    T2 nope() { ... }

    friend class Level;
};

class Level {
public:
    Physics physics;
};

Тогда

lvl.physics.gravity();
// but can't do lvl.physics.nope();, only Level can
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...