我有一个工厂,其中包含一个映射,其中对象名称作为键,函数指针作为值
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++ 将具有适当的编译时(或运行时)反射,并且这个答案将变得过时。到那时...
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*
的数组/向量,保存对象的直接基础。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 };
};
这就是这个答案的主要缺点:基类列表在类声明和
TypeInfo
构造之间重复。两者都必须手动一起更新(除非涉及宏,但这不会很漂亮)。
static_assert
中的2TypeInfoMaker
提供了一些防止错误的保护:如果基数不正确,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");
}