工厂中按类型过滤对象的方式

问题描述 投票:0回答:1

我有一个工厂,其中包含一个映射,其中对象名称作为键,函数指针作为值

template<class T>
static T* makeObj()
{
    return new T();
}

using createFunction = std::function<void*()>;
map<std::string, createFunction> types;

template<class T>
static void add(const std::string& name)
{
    types.insert(name, makeObj<T>);
}

我想添加一个附加函数来获取过滤的类型列表(获取继承类的列表),如下所示:

template<class baseType>
static std::list<std::string> getFilteredListOfTypes()
{
}

问题是如何去做。 第一个想法是创建一个以 struct 作为值的映射,并在那里存储适当类型的对象,我可以使用 dynamic_cast 并检查所有类型,但我怀疑创建对象是一个好主意。 另一种方法是使用 std::is_base_of:

template< class Base, class Derived >
struct is_base_of;

is_base_of 采用两种类型,我可以从 getFilteredListOfTypes 中获取类 Base,但如何获取类 Derived?创建一个对象并使用 decltype...但这里又创建了一个对象。

有没有办法在不创建对象的情况下创建这样的过滤函数? 在这里创建一个对象是不是一个坏主意?为地图的每个项目创建一个对象是一个坏主意吗?我有点害怕工厂会有数百种完全不小的类型。

UPD AppFactory.h:

class AppFactory
{
public:
    template<class T>
    static T* makeObj()
    {
        return new T();
    }

    using createFunction = std::function<void*()>;
    using registerMap = tsl::robin_map<std::string, createFunction>;

    static registerMap& get();

    template<class T>
    static std::unique_ptr<T> createUnique(const std::string& name)
    {
        return std::unique_ptr<T>(create<T>(name));
    }

    template<class T>
    static std::shared_ptr<T> createShared(const std::string& name)
    {
        return std::shared_ptr<T>(create<T>(name));
    }

    template<class T>
    static T* create(const std::string& name)
    {
        if(get().contains(name))
        {
            return static_cast<T*>(get()[name]());
        }
        return nullptr;
    }

    template<class T>
    static bool add(const std::string& name)
    {
        auto resultPair = get().insert_or_assign(name, makeObj<T>);
        return resultPair.second;
    }
};

#define APPFACTORY_ADD(classname) \
namespace { static bool addName = AppFactory::add<classname>(#classname); }

AppFactory.cpp:

AppFactory::registerMap& AppFactory::get()
{
    static AppFactory::registerMap map;
    return map;
}

工厂用途:

...
APPFACTORY_ADD(SomeClass);
...
AppFactory::createUnique<SomeBaseClass>("SomeClass")
...

更新2 getFilteredListOfTypes 的测试实现

struct typedata
{
    createFunction func;
    Object* obj;
};
using registerMap = tsl::robin_map<std::string, typedata>;
...
template<class T>
static bool add(const std::string& name)
{
    typedata data;
    data.func = makeObj<T>;
    data.obj = new T();
    auto resultPair = get().insert_or_assign(name, data);
    return resultPair.second;
}
...
template<class BaseType>
static std::list<std::string> getFilteredListOfTypes()
{
    std::list<std::string> typeList;
    for(auto it = get().begin(); it != get().end(); ++it)
    {
        if(dynamic_cast<BaseType*>(get()[it->first].obj))
        {
            typeList.push_back(it->first);
        }
    }
    return typeList;
}

getFilteredListOfTypes 的用法

class A : public Object
...
class B : public A
...
class C : public B
...
class D : public C
...
class E : public B
...
lst = AppFactory::getFilteredListOfTypes<A>(); // lst -> {"A", "B", "C", "D", "E"}
lst = AppFactory::getFilteredListOfTypes<B>(); // lst -> {"B", "C", "D", "E"}
lst = AppFactory::getFilteredListOfTypes<C>(); // lst -> {"C", "D"}
lst = AppFactory::getFilteredListOfTypes<D>(); // lst -> {"D"}
c++ templates factory factory-pattern
1个回答
0
投票

这个答案建议编写您自己的小型“运行时反射”框架。希望有一天,C++ 将具有适当的编译时(或运行时)反射,并且这个答案将变得过时。到那时...

TL;DR:现场演示(编译器浏览器)

基本反射框架

我们的目标是为工厂的每种返回类型生成一个非模板

TypeInfo
的实例。该类型有一个可以在运行时使用的成员函数
bool isBaseOf(TypeInfo const& other)
,其行为类似于
std::is_base_of

#include <span>

struct TypeTag{};
using TypeId =  TypeTag const*;

// This is NOT a templated class.
class TypeInfo {
public:
    using DirectBases = std::span<TypeInfo const* const>;

    explicit constexpr TypeInfo(TypeId typeId, DirectBases directBases)
        : typeId_{ typeId }
        , directBases_{ directBases }
    {}

    constexpr bool isBaseOf(TypeInfo const& child) const {
        if (*this == child) {
            return true;
        }
        for (TypeInfo const* directBase : child.directBases_) {
            if (isBaseOf(*directBase)) {
                return true;
            }
        }
        return false;
    }

    constexpr bool operator==(TypeInfo const& other) const {
        return typeId_ == other.typeId_;
    }
protected:
    TypeId typeId_;
    DirectBases directBases_;
};

以及

typename T
->
TypeInfo{...}
的编译时映射,充当反射信息数据库:

template<typename T>
struct TypeInfoOf;

template<typename T>
constexpr TypeInfo const& typeInfoOf() {
    return TypeInfoOf<T>::value;
}

有了这个,

getFilteredListOfTypes
可以实现为:

#include <list>
#include <string>
#include <unordered_map>

// NOTE (digression): I see a singleton anti-pattern here...
class AppFactory {
public:
    struct RegisterMapValue {
        // CreateFunction func;
        TypeInfo const* typeInfo;
    };

    using RegisterMap = std::unordered_map<std::string, RegisterMapValue>;

    static RegisterMap& get() {
        static RegisterMap map;
        return map;
    }

    template<class T>
    static void add(const std::string& name) {
        get()[name] = { /*func,*/ &typeInfoOf<T>() };
    }

    static std::list<std::string> getFilteredListOfTypes(TypeInfo const& base) {
        std::list<std::string> typeList;
        for(auto const& mapPair : get()) {
            if (base.isBaseOf(*mapPair.second.typeInfo)) {
                typeList.push_back(mapPair.first);
            }
        }
        return typeList;
    }

    template<class BaseType>
    static std::list<std::string> getFilteredListOfTypes() {
        return getFilteredListOfTypes(typeInfoOf<BaseType>());
    }
};

现在要构造这些 TypeId 对象,我们需要生成两个东西。

  • 每种类型都有一个独特的
    TypeId
    对象。
  • TypeInfo*
    的数组/向量,保存对象的直接基础。

生成唯一的TypeId

template<typename T>
struct UniqueId {
    static constexpr auto tag = TypeTag{};
};

template<typename T>
constexpr TypeId uniqueTypeIdOf() {
    return &UniqueId<T>::tag;
}

static_assert(uniqueTypeIdOf<std::string>() == uniqueTypeIdOf<std::string>());
static_assert(uniqueTypeIdOf<unsigned>() != uniqueTypeIdOf<std::string>());

生成基本数组

遗憾的是,我们无法自动获取类型的直接基数列表。我们将创建一个实用程序,将基本类型作为输入,将它们转换为

std::array<TypeInfo*>
对象,并构建
TypeInfo
对象:

#include <array>
#include <type_traits>

template<typename T, typename... DirectBases>
struct TypeInfoMaker {
private:
    static_assert((std::is_base_of_v<DirectBases,T> && ...));
    static_assert((!std::is_same_v<T,DirectBases> && ...));

    using BasesArray = std::array<TypeInfo const*, sizeof...(DirectBases)>;

    static constexpr auto directBases = BasesArray{ {&typeInfoOf<DirectBases>()...} };
public:
    static constexpr auto value = TypeInfo{ uniqueTypeIdOf<T>(), directBases };
};

填充 TypeInfoOf 映射

这就是这个答案的主要缺点:基类列表在类声明和

TypeInfo
构造之间重复。两者都必须手动一起更新(除非涉及宏,但这不会很漂亮)。

static_assert
中的2
TypeInfoMaker
提供了一些防止错误的保护:如果基数不正确,
TypeInfoMaker
将产生编译时错误(但会接受间接基数),并且应该防止循环引用(TypeInfo::isBaseOf将终止)。对于缺失的基地我们无能为力。

// User-provided class (unchanged)
class A {
public:
    virtual ~A() = default;
};

// The user has to declare A's reflection data as follows:
template<>
struct TypeInfoOf<A> : TypeInfoMaker<A> {};

class B : public A {};

template<>
struct TypeInfoOf<B> : TypeInfoMaker<B, A> {};

class C : public B {};

template<>
struct TypeInfoOf<C> : TypeInfoMaker<C, B> {};

class D : public C {};

template<>
struct TypeInfoOf<D> : TypeInfoMaker<D, C> {};

class E : public B {};

template<>
struct TypeInfoOf<E> : TypeInfoMaker<E, B> {};

从好的方面来说,填充

TypeInfoOf
地图只需要每种类型几行。

演示/使用

连接之前的所有代码片段并添加以下内容:

#include <iostream>
#include <string_view>

template<typename T>
void printFilteredList(std::string_view typeName) {
    std::cout << "filteredListOfTypes<" << typeName << ">: ";
    for (auto const& value : AppFactory::getFilteredListOfTypes<T>()) {
        std::cout << value << ' ';
    }
    std::cout << '\n';
}

int main() {
    AppFactory::add<A>("A");
    AppFactory::add<B>("B");
    AppFactory::add<C>("C");
    AppFactory::add<D>("D");
    AppFactory::add<E>("E");

    printFilteredList<A>("A");
    printFilteredList<B>("B");
    printFilteredList<C>("C");
    printFilteredList<D>("D");
    printFilteredList<E>("E");
}

现场演示(编译器浏览器)

© www.soinside.com 2019 - 2024. All rights reserved.