创建模板类型而不新建/删除

Creating template types without new/delete

本文关键字:新建 删除 建模 类型 创建      更新时间:2023-10-16

我有一个像这样的C++ Object类:

class Component {};
template <typename T>
concept component = std::is_base_of_v<Component, T>;
class Object
{
std::map<std::type_index, Component*> components;
public:
template<component T>
T* add()
{
if(components.find(typeid(T)) == components.cend())
{
T* value{new T{}};
components[typeid(T)] = static_cast<Component*>(value);
}
}
template<component T, typename... Args>
T* add(Args &&... args)
{
if(components.find(typeid(T)) == components.cend())
{
T* value{new T{std::forward<Args>(args)...}};
components[typeid(T)] = static_cast<Component*>(value);
}
}
};

添加到class ObjectComponentsdelete与我的问题无关的另一个函数上。AFAIK 执行大量new/delete调用(堆分配(会损害性能,据说应该有 20/30(甚至更多(Objects,每个调用有 3-10 个Object::add。我以为我可以在没有new的情况下调用T-s 构造函数,然后调用static_cast<Component*>(&value),但是映射上添加的组件是"无效的",这意味着所有 T 的成员(例如,在具有一些int成员的类上,它们都等于0而不是在其构造函数上传递的一些自定义值(。我知道value超出了范围,map上的指针变成了悬空的指针,但我找不到一种方法来实例化T对象而不调用new或不将它们声明为static.有什么办法可以做到这一点吗?

编辑:如果我value声明为static,一切都按预期工作,所以我想这是一个与value相关的终身问题。

我想,你认为这是创建对象的另一种方式

T value{std::forward<Args>(args)...};
components[typeid(T)] = static_cast<Component*>(&value);

这会在堆栈上创建一个局部变量。然后,执行赋值,将指向局部变量的指针存储在map中。

当您离开方法add()时,本地对象将被销毁,并且您在映射中有一个悬空指针。反过来,这最终会咬你。


只要你想存储指针,就没有办法绕过新建和删除。您可以使用某种内存池来缓解这种情况。

如果还可以在映射中存储对象而不是指针,则可以使用std::map::emplace就地创建组件。执行此操作时,还必须删除对delete的调用,并以其他方式清理对象。

在我看来,在证明堆分配确实会损害程序性能之前,试图避免堆分配不是一个好方法。如果是这种情况,您可能也应该摆脱代码中的std::map。话虽如此,如果您真的想在那里没有new/delete调用,则可以这样做,但需要显式枚举Component类型。像这样的东西可能是你正在寻找的:

#include <array>
#include <variant>
// Note that components no longer have to implement any specific interface, which might actually be useful.
struct Component1 {};
struct Component2 {};
// Component now is a variant enumerating all known component types.
using Component = std::variant<std::monostate, Component1, Component2>;
struct Object {
// Now there is no need for std::map, as we can use variant size 
// and indexes to create and access a std::array, which avoids more
// dynamic allocations.
std::array<Component, std::variant_size_v<Component> - 1> components;
bool add (Component component) {
// components elements hold std::monostate by default, and holding std::monostate
// is indicated by returning index() == 0.
if (component.index() > 0 && components[component.index() - 1].index() == 0) {
components[component.index() - 1] = std::move(component);
return true;
}
return false;
}
};

Component枚举所有已知的组件类型,这允许避免Object中的动态分配,但会增加内存使用量,因为用于单个Object的内存大致number_of_component_types * size_of_largest_component

虽然其他答案清楚地说明了问题所在,但我想提出一个建议,如何完全解决这个问题。

您在编译时知道 mosz 的映射中可能有哪些类型,因为您知道使用了add模板的哪个实例。因此,您可以摆脱地图并在编译时完成所有操作。

template<component... Comps>
struct object{
std::tuple<std::optional<Comps>...> components;
template<component comp, class ... args>
void add(Args... &&args) {
std::get<std::optional<comp>>(components).emplace(std::forward<Args>(args)...);
}
}

当然,这会迫使您在创建对象时收集所有可能的对象,但这不是更多信息,您必须拥有更多不切实际的信息。

您可以为add添加以下重载,以使错误更易于阅读

template<component T>
void add(...) {
static_assert(false, "Please add T to the componentlist of this object");
}