【问题标题】:Creating template types without new/delete创建没有 new/delete 的模板类型
【发布时间】:2020-09-29 04:37:30
【问题描述】:

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

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 ObjectComponents 在另一个与我的问题无关的函数上是deleted。 AFAIK 做很多 new/delete 调用(堆分配)会损害性能,并且应该有 20/30(甚至更多)Objectss,每个调用 3-10 个 Object::add。我以为我可以在没有new 的情况下调用T-s 构造函数,然后调用static_cast&lt;Component*&gt;(&amp;value),但是添加到地图上的组件是“无效的”,这意味着所有T 的成员(例如在具有一些@987654332 的类上@ 成员,它们都等于 0 而不是传递给其构造函数的一些自定义值)。我知道value 超出范围并且map 上的指针变成了一个悬空的指针,但是我找不到在不调用new 或不将它们声明为@987654338 的情况下实例化T 对象的方法@。有没有办法做到这一点?

编辑:如果我将value 声明为static,一切都会按预期进行,所以我猜这是与value 相关的终身问题。

【问题讨论】:

  • 您能解释一下您要解决的根本问题吗?
  • 对不起,我的错。我正在尝试删除那些new/delete 调用,但正如@Olaf Dietsche 所提到的,如果我编写他共享的sn-p,value 变量将超出范围,components 映射将包含悬空指针.如果我将value 声明为static,它将使程序的生命周期和问题得到解决,但我想找到一种更便宜的方法来创建Ts 并将它们存储在components 地图上(除了使用new /static).

标签: c++ visual-studio templates variadic-templates


【解决方案1】:

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

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

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

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


只要你想存储指针,就没有办法绕过 new 和 delete。您可以使用某种memory pool 来缓解这种情况。

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

【讨论】:

  • 我明白了。如果我在构造value 时添加static 代码工作正常(比如:c++ static T value{std::forward&lt;Args&gt;(args)...}; 所以这绝对是一个终身问题。我更新了问题信息,但我仍然必须找到比一些std::maps 更便宜的东西static 变量。
【解决方案2】:

在我看来,在您证明堆分配确实会损害您的程序性能之前尝试避免堆分配并不是一个好方法。如果是这种情况,您可能也应该在代码中去掉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

【讨论】:

  • 我需要组件派生结构来保存特定数据(例如,AnimationComponent 类将保存与动画相关的数据)。基类Component 只是用来为所有组件提供一个共同点,这样我就可以编写一个concept 以在add 中使用。我知道我的“要求”可能越界了,但是Component 派生类应该从#includes "Object.h" 的任何文件中写入(声明class Component 的文件) .这意味着任何人都应该修改using Component,这在我的情况下很容易出错。
【解决方案3】:

虽然其他答案清楚地表明了问题是什么,但我想提出一个建议,您如何才能完全解决这个问题。

您在编译时就知道 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");
}

【讨论】:

  • 事实上,在编译时我不知道映射将包含哪些确切类型,但我知道这些类型都派生自一个空的Component 类,因此我写了template&lt;component T&gt; 而不是 template&lt;class T&gt;。这个想法是我或代码的任何“用户”都可以从class Component 派生出他们自己的component 类型。除此之外,这个解决方案的想法很好,我认为std::optional 可以很容易地从代码中删除而没有任何副作用。
  • 只有在构造对象时可以构造所有类的情况下才能删除optional(例如,如果一切都是默认可构造的)。但是可选的没有问题,它不做任何堆分配,但基本上只是足够的空间用于对象和一个标志,如果它存在与否。如果您不知道哪些组件可以存在(不会存在,但可以存在),那么您的上述代码就有问题。由于对于您拥有的每个组件,您都需要将 add 模板实例化为正确的类型为 T。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-10
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 2020-05-16
  • 1970-01-01
相关资源
最近更新 更多