C ++如何在类中缓存模板类型T的变量?
Posted
技术标签:
【中文标题】C ++如何在类中缓存模板类型T的变量?【英文标题】:C++ How to cache a variable of template type T in a class? 【发布时间】:2021-10-10 04:37:13 【问题描述】:假设我有一个像这样的Foo
类,我需要它的许多实例。
class Foo
public:
Pool* bars; // a global list of bars, each may have a different type
template<typename T>
T& AddBar(int x)
return bars->emplace<T>(x);
template<typename T>
T& GetBar()
return bars->get<T>(); // a very slow function
Foo
的所有实例共享同一个柱线池,其中包含许多可能不同类型的柱线。例如,bars
可能是条形列表A bar1, B bar2, A bar3, C bar4
,其中ABC
是一些类类型,但每个Foo foo
实例只能有一个特定类型的条形,例如,foo
实例不能有两个A
类型的条形图。
给定一个实例Foo foo
,我可以使用foo.GetBar<A>()
、foo.GetBar<B>()
等获取特定类型的柱,但调用bars->get<T>()
函数既慢又昂贵。因此,我正在考虑缓存GetBar()
的结果,以便后续调用可以立即返回,而无需再次查询池。
现在这是我想出来的:我在成员函数内部创建了一个静态变量来存储 bar 的值,它只被初始化和赋值一次。
template<typename T>
T& GetBar()
static T bar ;
if (bar == T )
bar = bars->get<T>(); // a very slow function
return bar;
问题是,使用static
关键字,这个变量现在在Foo
的所有实例之间共享。如果我尝试从不同的实例中获取A
类型的条形,它们将返回相同的结果。
Foo foo1;
Foo foo2;
foo1.AddBar<A>(1);
foo2.AddBar<A>(2);
foo1.GetBar<A>(); // returns a bar (type = A, value = 1)
foo2.GetBar<A>(); // returns the same bar with value 1, not 2
如何在类中缓存T
类型的每个条形图并防止它被其他实例共享?我不知道如何将泛型类型存储为成员变量,此外,存储 bar 的每个类型 T
可能会很混乱。
编辑:我知道在调用方的类之外缓存结果会容易得多。我只是好奇类内部是否有一种优雅的缓存方式。
Edit2: bars
是指向注册表池的指针,其类型是复杂的数据结构,而不是原始列表或数组。澄清一下,我正在使用EnTT 库将实体组件系统集成到我的应用程序中,但不确定该池是如何在内部详细维护的。
Edit3:如果您想知道ABC
s 是什么,从概念上讲,这些类型在编译时是未知的。但需要在运行时确定。事实上,它们只是我实现的许多其他类类型,所以我也可以将它们硬编码到 Foo
类中,在这种情况下,我可能应该使用工厂模式和脚本语言来自动生成代码,但这会比首先使用泛型的目的。
【问题讨论】:
Pool* bars; // a global list of bars, each may have a different type
这不会解析。 bars
是一个指针。成为“列表”意味着什么?它是否指向Pool
类型的对象数组?在这种情况下,它们都是同一类型。
@n.1.8e9-where's-my-sharem。感谢您的评论,我已经编辑了帖子。
您需要创建一个能够存储不同类型实例的缓存对象。就像你有bar = bars->get<T>()
一样,你需要能够说bar = cache->get<T>()
和cache->put<T>(bar)
和if (cache->has<T>())
。这些在 C++ 中都不是免费的,您需要对其进行实际编程。一种可能的实现在内部使用std::map<std::type_info, std::any>
。
@n.1.8e9-where's-my-sharem。谢谢你的主意!我想std::map<Foo*, T>
可能是一个选择,我会试试看。
Foo
想要缓存的类型集合是在编译时定义的吗?例如。在你的例子中只有A
、B
和C
?
【参考方案1】:
在编写模型时,使用 n 的想法。 1.8e9-where's-my-share m.,对于您的“复杂的注册表池”,我写的实际可能是Foo
的实现。我留在那里Foo
只是为了也提供一些建议。如果你想要一个类型的变量不止一个,你当然必须改变映射的值类型,比如从std::any
到std::vector<std::any>
。否则,请进一步澄清您的问题。
#include <iostream>
#include <string>
#include <map>
#include <any>
struct Pool
template<typename T>
void emplace(T x)
this->elements_.insert_or_assign(typeid(T).hash_code(), std::make_any<T>(x));
template<typename T>
T& get()
return std::any_cast<T&>(elements_.at(typeid(T).hash_code()));
private:
std::map<std::size_t, std::any> elements_;
;
class Foo
public:
Foo(Pool& pool): bars_(pool)
void AddBar(int x)
return bars_.emplace<int>(x);
template<typename T>
T& GetBar()
return bars_.get<T>(); // a very slow function
private:
Pool& bars_;
;
int main()
Pool pool;
pool.emplace(4.3); pool.emplace(std::string("a value"));
Foo foo1(pool);
foo1.AddBar(3);
std::cout << foo1.GetBar<int>() << "\n";
【讨论】:
虽然我很好奇缓存是否可以在Foo
类中完成,而不是在池中,但我认为这个答案更清晰、更实用,我会将其标记为已接受。我还是 C++ 新手,所以不知道这是否是我想要的,但 O(1) 查找地图绝对是我能拥有的最好的。
好吧,std::map
查找在 O(log(n)) 中,但你可以使用 std::unordered_map
,但更重要的是,直到你没有至少,比如 10000 个条目容器,其实无所谓。事实上,通常std::vector
在大多数情况下是最快的数据结构,但映射帮助我们得到正确的代码。因为如果您的代码不正确,那么错误的速度有多快并不重要;-)【参考方案2】:
所有 ECS 实现都在其底层某个地方放弃了静态类型安全,尽管它们可以向用户隐藏丑陋的强制转换或使用 std::any
之类的东西,就像另一个不错的答案一样。
也就是说,这里有另一种方法来处理它(简化但它应该给你正确的想法),它避免了地图查找,除非在为新类型 T 调用 get 函数时:
#include <iostream>
#include <unordered_map>
#include <typeinfo>
#include <any>
class Foo
public:
template <class T>
T& get()
// Fetch a unique index for T to use for our std::vector.
const std::size_t n = type_index<T>();
// Resize if it's a new type we're encountering.
if (n >= bars.size())
bars.resize(n+1);
// Emplace if it's a former type of bar that's new for this instance
// of Foo.
if (!bars[n].has_value())
bars[n].emplace<T>();
// Returns the bar for that index cast to T&.
return std::any_cast<T&>(bars[n]);
private:
// Stores all the elements.
std::vector<std::any> bars;
// Returns a unique type index for T.
template <class T>
static std::size_t type_index()
// Using static here avoids repeat lookups into the hash map.
static const std::size_t n = lookup_type_index<T>();
return n;
// Looks up a unique type index for T.
template <class T>
static std::size_t lookup_type_index()
// Warning: hash_code is not guaranteed to be unique for all
// types in all compilers (two different types could return
// the same hash code, e.g.). I recommend using something else but
// that gets a bit involved (can expand and show you how if
// needed). Also consider a lock here for thread safety.
std::size_t key = typeid(T).hash_code();
auto it = idxs.find(key);
if (it != idxs.end())
return it->second;
idxs[key] = counter;
return counter++;
static inline std::unordered_map<std::size_t, std::size_t> idxs;
static inline std::size_t counter = 0;
;
int main()
using namespace std;
Foo f, f2;
f.get<int>() = 123;
f.get<double>() = 1.23;
f2.get<int>() = 456;
f2.get<double>() = 4.56;
cout << f.get<int>() << endl; // --> 123
cout << f.get<double>() << endl; // --> 1.23
cout << f2.get<int>() << endl; // --> 456
cout << f2.get<double>() << endl; // --> 4.56
我没有费心去测试它,但它应该给你这个想法的要点。 更新:我费心去测试它并用一个你可以运行的仓促程序更新它发现一些错别字只是仔细检查了我写的内容,并意识到我至少应该尝试编译我写的内容。 为了避免不断查看类型映射,我们将类型映射到向量中的索引。您可以按照原始示例所建议的那样使用子索引等对其进行扩展。以上说明了主要思想。
请注意上面代码中关于std::type_info::hash_code
的警告,因为它适用于我的答案和其他答案。我可以提供一种安全且便携的替代方案,它甚至不需要 RTTI,但会涉及到一些问题。如果您搜索在编译时将类型 T 可移植地映射到可在运行时使用的整数的方法,通常可以找到一堆示例。
【讨论】:
以上是关于C ++如何在类中缓存模板类型T的变量?的主要内容,如果未能解决你的问题,请参考以下文章