Согласование классов, наследования и обратных вызовов C - PullRequest
2 голосов
/ 17 февраля 2009

В моем проекте на C ++ я решил использовать библиотеку C. В моем рвении иметь хорошо продуманный и простой дизайн я закончил тем, что сделал небольшой шаг вперед. Частью моего требования к дизайну является то, что я могу легко поддерживать несколько API и библиотек для определенной задачи (прежде всего, из-за моего требования к кроссплатформенной поддержке). Итак, я решил создать абстрактный базовый класс, который бы равномерно обрабатывал данный набор библиотек.

Рассмотрим это упрощение моего дизайна:

class BaseClass
{
public:
    BaseClass() {}
    ~BaseClass() {}

    bool init() { return doInit(); }
    bool run() { return doWork(); }
    void shutdown() { destroy(); }
private:
    virtual bool doInit() = 0;
    virtual bool doWork() = 0;
    virtual void destroy() = 0;
};

И класс, который наследует от него:

class LibrarySupportClass : public BaseClass
{
public:
    LibrarySupportClass()
        : BaseClass(), state_manager(new SomeOtherClass()) {}

    int callbackA(int a, int b);
private:
    virtual bool doInit();
    virtual bool doWork();
    virtual void destroy();

    SomeOtherClass* state_manager;
};

// LSC.cpp:

bool LibrarySupportClass::doInit()
{
    if (!libraryInit()) return false;

    // the issue is that I can't do this:
    libraryCallbackA(&LibrarySupportClass::callbackA);

    return true;
}
// ... and so on

Проблема, с которой я столкнулся, состоит в том, что, поскольку это библиотека C, я должен предоставить C-совместимый обратный вызов в форме int (*)(int, int), но библиотека не поддерживает дополнительный указатель пользовательских данных для этих обратные вызовы. Я бы предпочел бы делать все эти обратные вызовы внутри класса, потому что класс несет объект состояния.

То, что я в итоге сделал, это ...

static LibrarySupportClass* _inst_ptr = NULL;
static int callbackADispatch(int a, int b)
{
    _inst_ptr->callbackA(a, b);
}

bool LibrarySupportClass::doInit()
{
    _inst_ptr = this;

    if (!libraryInit()) return false;

    // the issue is that I can't do this:
    libraryCallbackA(&callbackADispatch);

    return true;
}

Это явно будет плохо, если библиотека LibrarySupportClass будет создана более одного раза, поэтому я подумал об использовании одноэлементного дизайна, но по этой одной причине я не могу оправдать этот выбор.

Есть ли лучший способ?

Ответы [ 6 ]

3 голосов
/ 17 февраля 2009

Внешнее ограничение библиотеки c диктует, что при вызове вашего обратного вызова у вас нет идентификатора «собственного» экземпляра обратного вызова. Поэтому я думаю, что ваш подход правильный.

Я бы предложил объявить метод callbackDispatch статическим членом класса и сделать сам класс синглтоном (есть много примеров того, как реализовать синглтон). Это позволит вам реализовать аналогичные классы для других библиотек.

3 голосов
/ 17 февраля 2009

Вы можете обосновать этот выбор: ваше оправдание заключается в том, что библиотека C поддерживает только один экземпляр обратного вызова.

Синглеты пугают меня: не ясно, как правильно уничтожить синглтон, а наследование только усложняет дело. Я еще раз посмотрю на этот подход.

Вот как бы я это сделал.

LibrarySupportClass.h

class LibrarySupportClass : public BaseClass
{
public:
    LibrarySupportClass();
    ~LibrarySupportClass();
    static int static_callbackA(int a, int b);
    int callbackA(int a, int b);
private:
    //copy and assignment are rivate and not implemented
    LibrarySupportClass(const LibrarySupportClass&);
    LibrarySupportClass& operator=(const LibrarySupportClass&);
private:
    static LibrarySupportClass* singleton_instance;
};

LibrarySupportClass.cpp

LibrarySupportClass* LibrarySupportClass::singleton_instance = 0;


int LibrarySupportClass::static_callbackA(int a, int b)
{
  if (!singleton_instance)
  {
    WHAT? unexpected callback while no instance exists
  }
  else
  {
    return singleton_instance->callback(a, b);
  }
}

LibrarySupportClass::LibrarySupportClass()
{
  if (singleton_instance)
  {
    WHAT? unexpected creation of a second concurrent instance
    throw some kind of exception here
  }
  singleton_instance = this;
}

LibrarySupportClass::~LibrarySupportClass()
{
  singleton_instance = 0;
}

Я хочу сказать, что вам не нужно указывать ему внешний интерфейс канонического «синглтона» (что, например, затрудняет его уничтожение).

Вместо этого тот факт, что существует только один из них, может быть частной деталью реализации, и обеспечивается частной реализацией (например, оператором throw в конструкторе) ... при условии, что код приложения уже таков, он не будет пытаться создать более одного экземпляра этого класса.

Наличие такого API (вместо более канонического «синглтонного» API) означает, что вы можете, например, создать экземпляр этого класса в стеке (если вы не пытаетесь создать более одного этого).

1 голос
/ 17 февраля 2009

Проблема, с моей точки зрения, состоит в том, что, поскольку ваш метод не является статичным, вы можете очень легко получить внутреннее состояние в функции, которая не должна иметь такого состояния, поскольку, поскольку на начало файла, может быть перенесено между вызовами, что очень плохо (тм). По крайней мере, как предложил Дани выше, все методы, которые вы вызываете из вашего обратного вызова C, должны быть статическими, чтобы гарантировать, что от вызова вашего обратного вызова не останется остаточного состояния.

Выше предполагается, что вы объявили static LibrarySupportClass* _inst_ptr в самом верху. В качестве альтернативы рассмотрите возможность использования фабричной функции, которая будет создавать рабочие копии ваших LibrarySupportClass по запросу из пула. Эти копии могут затем вернуться в пул после того, как вы поработаете с ними, и их можно будет использовать повторно, чтобы вы не занимались созданием экземпляра каждый раз, когда вам нужны эти функции.

Таким образом, вы можете сохранять свои объекты в состоянии во время одного вызова обратного вызова, так как будет ясный момент, когда ваш экземпляр будет освобожден и получит зеленый свет для повторного использования. Вы также окажетесь в гораздо лучшем положении для многопоточной среды, и в этом случае каждый поток получит свой собственный экземпляр LibrarySupportClass.

1 голос
/ 17 февраля 2009

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

Конечно, это имеет проблему с производительностью, если у вас много экземпляров, и вам нужно перебирать весь список.

0 голосов
/ 17 февраля 2009

Может ли ваш обратный вызов выбрать экземпляр на основе a и / или b? Если это так, то зарегистрируйте классы поддержки своей библиотеки в глобальной / статической карте, а затем callbackADispatch() найдите правильный экземпляр на карте.

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

0 голосов
/ 17 февраля 2009

Проблема, с которой я столкнулся, состоит в том, что, поскольку это библиотека C, я должен предоставить C-совместимый обратный вызов в форме int (*) (int, int), но библиотека не поддерживает дополнительный указатель пользовательских данных для этих обратных вызовов

Можете ли вы уточнить? Является ли проблема выбора типа обратного вызова на основе пользовательских данных?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...