Указатель функции из QMap

Я пытаюсь реализовать шаблон фабричного метода в своем проекте QT, следуя этому примеру: https://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

#include <QCoreApplication>
#include <QDebug>

class IAnimal
{
public:
    virtual int GetNumberOfLegs() const = 0;
    virtual void Speak() = 0;
    virtual void Free() = 0;
};
typedef IAnimal* (__stdcall *CreateAnimalFn)(void);

// IAnimal implementations
class Cat : public IAnimal
{
public:
    int GetNumberOfLegs() const { return 4; }
    void Speak() { qDebug() << "Meow" << endl; }
    void Free() { delete this; }

    static IAnimal * __stdcall Create() { return new Cat(); }
};

class Dog : public IAnimal
{
public:
    int GetNumberOfLegs() const { return 4; }
    void Speak() { qDebug() << "Woof" << endl; }
    void Free() { delete this; }

    static IAnimal * __stdcall Create() { return new Dog(); }
};

Заводской класс:

// Factory for creating instances of IAnimal
class AnimalFactory
{
private:
    AnimalFactory();
    AnimalFactory(const AnimalFactory &) { }
    AnimalFactory &operator=(const AnimalFactory &) { return *this; }

    typedef QMap<QString,CreateAnimalFn> FactoryMap;
    FactoryMap m_FactoryMap;
public:
    ~AnimalFactory() { m_FactoryMap.clear(); }

    static AnimalFactory *Get()
    {
        static AnimalFactory instance;
        return &instance;
    }

    void Register(const QString &animalName, CreateAnimalFn pfnCreate);
    IAnimal *CreateAnimal(const QString &animalName);
};

AnimalFactory::AnimalFactory()
{
    Register("Cat", &Cat::Create);
    Register("Dog", &Dog::Create);
}

void AnimalFactory::Register(const QString &animalName, CreateAnimalFn pfnCreate)
{
    m_FactoryMap[animalName] = pfnCreate;
}

IAnimal *AnimalFactory::CreateAnimal(const QString &animalName)
{
    FactoryMap::iterator it = m_FactoryMap.find(animalName);
    if( it != m_FactoryMap.end() )
    return it.value();
    return NULL;
}

Однако я сталкиваюсь с такой ошибкой:

cannot convert 'IAnimal* (__attribute__((__stdcall__)) *)()' to 'IAnimal*' in return
         return it.value();

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

Буду очень благодарен за любой совет.


person Agrowal    schedule 18.04.2018    source источник
comment
return it.value()(); После того, как вы получили указатель на функцию с помощью it.value(), вам нужно вызвать функцию.   -  person Igor Tandetnik    schedule 19.04.2018
comment
Аааа так просто. В любом случае большое спасибо вам, это сработало!   -  person Agrowal    schedule 19.04.2018
comment
@IgorTandetnik считает, что нужно дать реальный ответ, это поможет будущему читателю получить принятый ответ на вопрос :)   -  person ymoreau    schedule 19.04.2018


Ответы (1)


Это немного сложно. Вы пишете C++, поэтому вам не следует копировать Java. С++ здесь более выразителен.

  1. Вам не понадобятся явные методы Create/Free — компилятор может сгенерировать их автоматически.
  2. Обязательно нужен виртуальный деструктор, иначе интерфейс будет бесполезен. Любой класс, от которого вы хотите создать производный класс, должен иметь виртуальный деструктор с очень немногими специализированными исключениями из этого правила.
  3. Все реализации виртуальных методов должны быть объявлены override, включая деструктор, но не virtual, так как это нарушит DRY.
  4. Классы могут носить свои имена, так что фабрика может зарегистрировать их, просто зная их типы. Это необязательное поведение фабрики.
#include <QtCore>

class IAnimal {
public:
    virtual int GetNumberOfLegs() const = 0;
    virtual QString Speaks() = 0;
    virtual ~IAnimal() {}
};

class Cat : public IAnimal {
public:
    int GetNumberOfLegs() const override { return 4; }
    QString Speaks() override { return QStringLiteral("Meow"); }
    static auto className() { return "Cat"; }
};

class Dog : public IAnimal {
public:
    int GetNumberOfLegs() const override { return 4; }
    QString Speaks() override { return QStringLiteral("Woof"); }
    static auto className() { return "Dog"; }
};

Теперь у нас может быть универсальная фабрика. Обратите внимание, что все разумные типы контейнеров C++ управляют своими данными. Вам не нужно явно очищать их при уничтожении. Мы используем C++11. Метод Register будет принимать только типы, производные от Interface, и этот метод автоматически создает функцию построения с использованием лямбда-выражения.

Время жизни экземпляра должно явно контролироваться путем его создания в main().

#include <type_traits>
#include <typeindex>
#include <map>

template <class Interface> class Factory {
  template <class C, class T = void> struct enable_if_I :
    std::enable_if<std::is_base_of<Interface, C>::value, T> {};
  using create_fn = Interface* (*)();
  std::map<QByteArray, create_fn, std::less<>> m_creators;
  std::map<std::type_index, QByteArray> m_names;
  static Factory *&instance_ref() { // assume no inline static yet
    static Factory *m_instance;
    return m_instance;
  }
  Factory(const Factory &) = delete;
  Factory &operator=(const Factory &) = delete;
public:
  Factory() {
    Q_ASSERT(!instance());
    instance_ref() = this;
  }
  virtual ~Factory() { instance_ref() = {}; }

Как правило, для регистрации требуются тип и имя производного класса. Это ничего не предполагает о том, есть ли в классе className() членов. Фабрика хранит как фабричную функцию, так и имя. Это позволяет искать имя без использования className в качестве виртуального метода интерфейса.

  template <class T> typename enable_if_I<T>::type Register(const QByteArray &name) {
    m_creators[name] = +[]()->Interface* { return new T(); };
    m_names[{typeid(T)}] = name;
  }

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

  template <class T1> typename enable_if_I<T1>::type Register() {
    this->Register<T1>(T1::className());
  }
  template <class T1, class T2, class...T> typename enable_if_I<T1>::type Register() {
    this->Register<T1>(T1::className());
    this->Register<T2, T...>();
  }

Методы создания экземпляра оптимизированы таким образом, чтобы не копировать заданное имя, независимо от формата. Вот почему мы используем карту std::map<K, V, std::less<>> с прозрачным компаратором. QByteArray предоставляет operator<, который принимает различные типы в правой части, и чтобы использовать это, тип ключа (здесь: имя) должен попасть в компаратор.

  template <typename T> static Interface *CreateA(T &&t) {
      return instance() ? instance()->Create(std::forward<T>(t)) : nullptr;
  }
  Interface *Create(QLatin1String name) const { return Create(name.data()); }
  template <typename T> Interface *Create(T &&name) const;
  static const QByteArray &NameOfA(const Interface * obj);
  const QByteArray &NameOf(const Interface *) const;
  static Factory *instance() { return instance_ref(); }
};

template <class Interface>
template <typename T> Interface *Factory<Interface>::Create(T &&name) const {
  auto it = m_creators.find(name);
  return (it != m_creators.end()) ? it->second() : nullptr;
}
namespace detail { 
const QByteArray & null() { static const QByteArray n; return n; }
}
template <class Interface>
const QByteArray &Factory<Interface>::NameOfA(const Interface *obj) {
  return instance() ? instance()->NameOf(obj) : detail::null();
}
template <class Interface>
const QByteArray &Factory<Interface>::NameOf(const Interface *obj) const {
  auto it = m_names.find(typeid(*obj));
  return (it != m_names.end()) ? it->second : detail::null();
}

Универсальная фабрика берет интерфейс и конкретные типы и регистрирует их все в конструкторе. Это упрощает строительство заводов.

template <class Interface, class ...Types>
class GenericFactory : public Factory<Interface> {
public:
  GenericFactory() {
    this->template Register<Types...>();
  }
};

using AnimalFactory = GenericFactory<IAnimal, Cat, Dog>;

Пример использования с утверждениями для указания желаемого поведения. Обратите внимание, что для уничтожения объектов достаточно delete их экземпляра. Компилятор будет генерировать вызовы.

int main() {
  Q_ASSERT(!AnimalFactory::instance());
  {
    AnimalFactory animals;
    Q_ASSERT(AnimalFactory::instance());
    auto *dog1 = AnimalFactory::CreateA("Dog");
    Q_ASSERT(dynamic_cast<Dog*>(dog1));
    Q_ASSERT(AnimalFactory::NameOfA(dog1) == Dog::className());
    Q_ASSERT(dog1->Speaks() == QStringLiteral("Woof"));
    auto *dog2 = AnimalFactory::CreateA(QLatin1String("Dog"));
    Q_ASSERT(dynamic_cast<Dog*>(dog2));
    auto *cat = AnimalFactory::CreateA("Cat");
    Q_ASSERT(dynamic_cast<Cat*>(cat));
    Q_ASSERT(cat->Speaks() == QStringLiteral("Meow"));
    Q_ASSERT(AnimalFactory::NameOfA(cat) == Cat::className());
    delete cat;
    delete dog2;
    delete dog1;
  }
  Q_ASSERT(!AnimalFactory::instance());
}
person Kuba hasn't forgotten Monica    schedule 20.04.2018
comment
Я забыл, что типы возвращаемых значений автономных функций не поддерживают ковариацию. Исправлена. - person Kuba hasn't forgotten Monica; 22.04.2018