简体   繁体   中英

Function pointer from QMap

I'm trying to implement factory method pattern in my QT project following this example: 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 class:

// 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;
}

However I am encountering such an error:

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

Only existing anwser ( Insert function pointer into QMap (Qt) ) suggests making Create() functions static which doesn't seem to help.

I will be very grateful for any piece of advice.

That's a little bit complicated. You're writing C++, so you shouldn't be copying Java. C++ is way more expressive here.

  1. You won't need the explicit Create / Free methods - the compiler can generate them for you, automatically.
  2. You definitely need a virtual destructor, otherwise the interface will be useless. Any class you intend to derive from must have a virtual destructor, with very few specialized exceptions from this rule.
  3. All implementations of virtual methods should be declared override , including the destructor, but not virtual as that'd violate DRY.
  4. The classes can carry their names, so that the factory can register them just by knowing their types. This is an optional behavior of the factory.
#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"; }
};

Now we can have a generic factory. Note that all sane C++ container types manage their data. You don't need to explicitly clear them on destruction. We're leveraging C++11. The Register method will only accept types that derive from Interface , and that method automatically generates a construction function using the lambda expression.

The lifetime of the instance should be controlled explicitly by instantiating it in 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() = {}; }

In general, registration requires the type and name of the derived class. This presupposes nothing about whether the class has a className() member. The factory stores both the factory function and the name. This allows name look up without having className as a virtual method of the interface.

  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;
  }

When class names are known, we can leverage them to register one or more classes, given just their types.

  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...>();
  }

The instance creation methods are optimized not to copy the name given, no matter the format. This is why we use the std::map<K, V, std::less<>> map with a transparent comparator. QByteArray provides operator< that takes various types on the right-hand side, and to exploit this, the type of the key (here: name) must reach the comparator.

  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();
}

The generic factory takes the interface and concrete types and registers them all in the constructor. This makes building the factories simple.

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

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

Example of use, with asserts to indicate desired behavior. Note that to destroy the objects, one merely has to delete their instance. The compiler will generate the calls.

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());
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM