在没有 new/delete 的情况下创建模板类型
Creating template types without new/delete
我有一个像这样的 C++ 对象 class:
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 Object
的 Component
s 在与我的问题无关的另一个函数上 delete
d。 AFAIK 进行大量 new
/delete
调用(堆分配)会损害性能,并且应该有 20/30(或更多)Objects
s 和 3-10 Object::add
每一个。我以为我可以在没有 new
的情况下调用 T
-s 构造函数,然后调用 static_cast<Component*>(&value)
,但是在地图上添加的组件是 "invalid",这意味着所有 T 的成员(例如. 在具有一些 int
成员的 class 上,它们都等于 0
而不是传递给其构造函数的某些自定义值)。我知道 value
超出了范围并且 map
上的指针变成了悬空指针,但是我找不到不调用 [=15= 就实例化 T
对象的方法] 或不将它们声明为 static
。有什么办法吗?
编辑:如果我将 value
声明为 static
,一切都按预期工作,所以我猜它是与 value
.
相关的终身问题
我想,您认为这是创建对象的另一种方式
T value{std::forward<Args>(args)...};
components[typeid(T)] = static_cast<Component*>(&value);
这会在堆栈上创建一个局部变量。然后进行赋值,将指向局部变量的指针存储在 map
.
中
当你离开方法add()
时,本地对象将被销毁,并且你在映射中有一个悬空指针。反过来,这最终会咬你一口。
只要你想存指针,就没法绕过new和delete。您可以使用某种 memory pool.
来缓解这种情况
如果您还可以在地图中存储对象而不是指针,则可以使用 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");
}
我有一个像这样的 C++ 对象 class:
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 Object
的 Component
s 在与我的问题无关的另一个函数上 delete
d。 AFAIK 进行大量 new
/delete
调用(堆分配)会损害性能,并且应该有 20/30(或更多)Objects
s 和 3-10 Object::add
每一个。我以为我可以在没有 new
的情况下调用 T
-s 构造函数,然后调用 static_cast<Component*>(&value)
,但是在地图上添加的组件是 "invalid",这意味着所有 T 的成员(例如. 在具有一些 int
成员的 class 上,它们都等于 0
而不是传递给其构造函数的某些自定义值)。我知道 value
超出了范围并且 map
上的指针变成了悬空指针,但是我找不到不调用 [=15= 就实例化 T
对象的方法] 或不将它们声明为 static
。有什么办法吗?
编辑:如果我将 value
声明为 static
,一切都按预期工作,所以我猜它是与 value
.
我想,您认为这是创建对象的另一种方式
T value{std::forward<Args>(args)...};
components[typeid(T)] = static_cast<Component*>(&value);
这会在堆栈上创建一个局部变量。然后进行赋值,将指向局部变量的指针存储在 map
.
当你离开方法add()
时,本地对象将被销毁,并且你在映射中有一个悬空指针。反过来,这最终会咬你一口。
只要你想存指针,就没法绕过new和delete。您可以使用某种 memory pool.
来缓解这种情况如果您还可以在地图中存储对象而不是指针,则可以使用 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");
}