【问题标题】:Storing pointer to heap objects in an STL container for later deallocation将指向堆对象的指针存储在 STL 容器中以供以后释放
【发布时间】:2012-10-31 15:53:22
【问题描述】:

如何在一个 STL 容器中存储任意数量的(不同类型的)动态创建的实例,以便以后只有容器才能释放内存?

它应该像这样工作:

std::vector< void * > vec;
vec.push_back( new int(10) );
vec.push_back( new float(1.) );

现在,如果vec 超出范围,则指向实例的指针将被破坏,但不会释放intfloat 的内存。显然我做不到:

for( auto i : vec )
  delete *i;

因为void* 不是指向对象的类型。

您可以反对并争辩说这不是一个好主意,因为无法访问向量的元素。没错,我自己也不会访问它们。 NVIDIA 驱动程序将访问它们,因为它只需要地址(void* 很好)作为内核调用的参数。

我想这里的问题是它可以存储不同的类型。想知道union 是否可以解决问题,以防有人想将其作为参数传递给 cuda 内核。

内核采用不同类型的参数,并通过遍历您事先不知道类型的表达式树(表达式模板)来收集。因此,在访问叶子时,您将存储参数。只能是 void*,以及内置类型 int、float 等。

可以在内核启动后立即删除向量(启动是异步的,但驱动程序首先复制参数然后继续主机线程)。第二个问题:每个参数都传递一个 void* 给驱动程序。无论它是 int、float 还是 void*。所以我想一个人可以分配比需要更多的内存。我认为工会的东西可能值得一看。

【问题讨论】:

  • 你为什么需要这么疯狂的设计?只需使用vector&lt;float&gt;vector&lt;int&gt;。如果您不知道它指向的对象的类型,void * 有什么用?
  • 里面的类型是否有限?
  • 内核接受不同类型的参数,通过遍历事先不知道类型的表达式树(表达式模板)来收集。因此,在访问叶子时,您将存储参数。可能是intfloat
  • @Caribou 是的,只能是void*,以及内置类型intfloat
  • 你能不能 malloc 它,然后释放它,然后在新的地方而不是直接新建和删除?

标签: c++ stl c++11 cuda


【解决方案1】:

您可以使用您想要支持的每种类型的一个向量。

虽然这对 void* 向量的想法有了很大的改进,但它仍然很臭。

这听起来确实像一个 XY 问题:你有一个问题 X,你设想了一个解决方案 Y,但是如果没有某种巧妙的适应,Y 显然是行不通的,所以问问 Y。相反,应该询问真正的问题 X。哪个是?

【讨论】:

  • 不能为每种类型使用一个向量。请参阅对问题的评论。
  • 遍历解析树是在编译时完成的。在库开发时,存储到向量中的类型是未知的。这是图书馆(活动图书馆,在 Todd Veldhuizen 的意义上)。这取决于用户代码(使用库的代码)要存储什么类型。
  • 好吧,我试着问一下真正的问题。我有一系列不同类型的值,我需要它们的地址作为void* 的数组,完全按照序列的顺序。好在类型只能是 void 指针或内置类型。
【解决方案2】:

好的,FWIW

我建议使用结合malloc 的就地新的。这样做是允许您将创建为 void* 的指针存储在您的向量中。然后,当向量完成时,可以简单地对其进行迭代并调用free()

I.E.

void* ptr = malloc(sizeof(int));
int* myNiceInt = new (ptr) int(myNiceValue);
vec.push_back(ptr);

//at some point later iterate over vec
free( *iter );

我相信这将是在这种情况下问题的最简单解决方案,但请接受这是一个类似“C”的答案。

只是说;)

【讨论】:

  • 当我们这样做时,我们可以分配一对释放器函数和数据,然后将指向第二个向量中的指针作为 void。当您想释放时,撤消偏移并取回该对。 ;)
【解决方案3】:

无论如何,“NVIDIA 驱动程序”听起来像是一个 C 接口,所以malloc 并不是一个疯狂的建议。

正如您所建议的,另一种选择是使用联合...但是您还需要将“标签”存储在并行向量中以记录元素的实际类型,以便您可以转换为适当的类型删除时。

简而言之,您必须先将void * 转换为适当的类型,然后才能对其进行delete。 “C++ 方式”是拥有一个带有虚拟析构函数的基类;当它指向任何子类的实例时,您可以调用delete。但是如果你使用的库已经确定了类型,那就不行了。

【讨论】:

    【解决方案4】:

    如果您可以控制类型,则可以为它们创建抽象基类。给那个类一个虚拟析构函数。然后你可以拥有你的std::vector&lt;Object*&gt; 并对其进行迭代以删除从 Object 继承的任何内容。

    您可能需要第二个带有指向实际值的指针的std::vector&lt;void*&gt;,因为Object* 可能首先命中vtable。像virtual void* ptr() { return &amp;value; } 这样的第二个虚函数在这里会很有用。如果它需要对象的大小,您也可以添加它。

    你可以像这样使用模板模式:

    template<typename T>
    class ObjVal : public Object {
        public:
        T val;
        virtual void* ptr() { return &this->val; }
        virtual size_t size() { return sizeof(this->val); }
    };
    

    那么你只需要输入一次。

    这并不是特别节省内存,因为每个 Object 至少为 vtable 选择了一个额外的指针。

    但是,new int(3) 的内存效率也不是很高,因为您的分配器可能使用了超过 4 个字节。添加该 vtable 指针可能基本上是免费的。

    【讨论】:

      【解决方案5】:

      使用超过 1 个向量。保持vector&lt;void*&gt; 与API 对话(我猜这需要一个连续的非统一类型的void*s 块?),但也有一个vector&lt;std::unique_ptr&lt;int&gt;&gt;vector&lt;std::unique_ptr&lt;float&gt;&gt; 拥有数据。创建new int 时,将拥有内存的unique_ptr 推送到ints 的vector 中,然后将其作为void* 粘贴在API 兼容的vector 上。将三个 vectors 捆绑到一个 struct 中,以便尽可能将它们的生命周期绑定在一起(并且可能是这样)。

      您也可以使用存储变量所有权的单个向量来执行此操作。 vector 的滚动你自己的 RAII 伪unique_ptr,或带有自定义驱逐舰的 shared_ptr,或 vectorstd::function&lt;void()&gt;,你的“捆绑”结构的驱逐舰调用,或者你有什么。但我不推荐这些选项。

      【讨论】:

        猜你喜欢
        • 2013-03-14
        • 1970-01-01
        • 1970-01-01
        • 2014-09-05
        • 2019-01-26
        • 2011-07-08
        • 2013-07-28
        • 1970-01-01
        相关资源
        最近更新 更多