【问题标题】:Does delete need to be used for new'd arrays created in vector?在向量中创建的新数组是否需要使用删除?
【发布时间】:2018-06-23 13:19:57
【问题描述】:

我正在尝试为 void** 数据数组创建一些动态数组。

std::vector<void*> data;
data.push_back(new double[1024]);  // array of doubles
data.push_back(new short[1024]);   // array of shorts

为了清理,我应该只使用

data.clear();

或者是否需要删除每个新的,如果需要,如何完成?

【问题讨论】:

  • 真正的问题是你将如何使用这段代码?如果你只有一个void *,你怎么知道向量中存储了什么类型?
  • 需要 void** 的函数采用类型数组的另一个参数。
  • 您是否尝试连接到C 函数或基于C 的API?我认为没有理由为 C++ 程序设计这样的设计。
  • 是的,它是与 C API 的接口,并且代码实际上是使用 c++/cli,但 vector 和 c++/cli ref 类似乎并不混合......
  • API 可能知道什么类型,但是您的代码会丢失所有类型信息,因此发出对delete [] 的调用将在笨拙的编码到不可能之间。

标签: c++ arrays pointers vector void-pointers


【解决方案1】:

为了清理我应该使用

data.clear();

这将从向量中删除所有指针。如果其中任何一个是唯一指向它们各自的动态对象的副本,那么这些对象就不能再被销毁,也不能释放它们的内存。这称为内存泄漏。

或者是否需要删除每个新的

是的。每次执行new[] 表达式时,必须恰好有一个delete[]。如果没有delete[],则说明存在泄漏。

如果是这样,那是怎么做的?

您只需删除每个指针。您必须格外小心,不要在不先删除它的情况下意外覆盖或删除该向量的元素。您还必须注意不要多次删除相同的元素(或其副本)。

此外,不能删除指向void 的指针。指向 void 的指针可以转换为任何数据指针类型,但您必须知道每个元素的类型。一个工作示例

delete[] static_cast<double*>(data[0]);
delete[] static_cast<short* >(data[1]);

存储拥有内存的裸指针通常是一个非常糟糕的主意——为此使用指向void 的指针更是如此。更好的设计是将动态数组存储在 RAII 容器中,例如std::vector

std::vector<double> doubles(1024);
std::vector<short>  shorts (1024);
std::vector<void*>  data{doubles.data(), shorts.data()};

这样数组归向量所有,内存由std::vector的代码管理。您必须注意,只要data 的元素仍然指向它们,就不能破坏doublesshorts

【讨论】:

  • @codebender 我在编辑中添加的最终建议确实解决了这个问题。
  • delete[] static_cast&lt;type*&gt;(data[i]); 可以用于自定义结构、typedef 甚至是 char**(char 数组数组)吗?
  • @codebender 确定。如果将custom_struct* 转换为void*,则可以使用static_cast 将其转换回来。同样适用于所有数据指针(但不适用于成员或函数指针)。
  • 内部数组元素是否应该像 (static_cast&lt;type*&gt;(data[i]))[j]; 那样可寻址,其中 j 是内部数组索引?
  • 我会将最后一个代码片段移到顶部,这是这个问题的真正答案
【解决方案2】:

不,这行不通

很抱歉直截了当,但是像您一样使用void* 是一种非常糟糕的做法。这种类似 C 的快捷方式在 C++ 中无法正常工作,至少在您使用其他基本类型的数组时不会!

因为将指向对象的指针强制转换为 void 指针,所以编译器无法生成 C++ 对象生命周期所需的代码。

所以让我们使用一个帮助类来演示问题所在:

class C1 {
public: 
    C1() { cout<<"  C1 object constructed"<<endl; } 
    ~C1() { cout<<"  C1 object destroyed"<<endl; }
};

Here 问题的在线演示,我将在以后逐步解释。

案例1:使用指针手动分配

在数组中分配的每一个C1,都会被创建和销毁,简单的代码如下:

C1 *p = new C1[2];   // every new []
delete[] p;          // requires delete[]

案例 2:手动分配推入指针向量:

当一个向量被销毁或被清除时,它的内容也会被销毁。但是,使用指针向量时,指针会被销毁,而不是指向的对象:

所以这行不通;它会泄漏内存:

   vector<C1*> v; 
   v.push_back(new C1[2]);   // C1 objects are created 
   v.clear();                // pointers are lost but C1 objects are not derleted.  

如果在clear之前插入如下语句,一切都OK了:所有创建的C1都被销毁了:

   for (auto &x:v)
       delete[]x; 
   v.clear(); 

案例 3:void* 替代方案注定要失败:

如果我们采用上面的工作代码,但使用转换为void*,C1 对象会被创建,但不会被销毁:

std::vector<void*> data;
data.push_back(new C1[2]);   // yes, creating works and ponter is in ector
for (auto &x:data) 
       delete[]x;           // ouch, deletes a void* so it doesn't know that it's a C1 object
data.clear();

如何解决?

嗯,您需要为存储在向量中的对象提供一个公分母,例如:

  • 对所有元素使用继承和公共基类型。
  • 使用带有类型选择器的联合,以便能够知道指向的对象的类型
  • 使用boost::variant对象

如果您有一个通用的基本类型,您可以考虑使用向量替代向量来摆脱手动内存管理。

【讨论】:

  • 所以 delete[] static_cast&lt;double*&gt;(data[0]); 在另一个答案中的建议不起作用?
  • 会的。但问题是:你怎么知道向量元素中哪个是double*,哪个是int*,哪个是C1*?
  • c API 函数需要另一个类型的数组,因此它们是已知的,并且可以使用一个开关来遍历每个类型。
【解决方案3】:

是的,对于您的情况,您必须通过operator delete 明确释放它们。但是,由于您似乎想要存储指向不同类型数组的指针,您打算如何区分它们?

忽略这一事实,您的示例代码尖叫std::unique_ptr

【讨论】:

  • 您能否详细说明为什么 std::unique_ptr 可能会更好?
  • 自动生命周期管理,所以只要指针从你的向量中删除,它就会被隐式删除。
  • 使用 unique_ptr 指定要使用的删除器当然是个好主意。但是,您能否展示一个允许将 unique_ptr 组合到同一向量中的不同类型的示例?
  • 对,我没有想到不能假设通过同一个删除器删除不同的类型是无效的(在基本类型的情况下可能会起作用,但我不会建议做出这样的假设)。我想在这种情况下std::variant 效果最好。
【解决方案4】:

经验法则是删除您使用new 分配的所有内容 当您传递使用new[] 分配的数组的引用时,您必须使用delete[] 删除它。但是三思而后行,您正在错误地使用分配的引用....因为您没有将其保存在任何地方,而vector 也没有,它只是可以导航另一个容器(如果可迭代)来制作所有的副本其他容器元素(这是一种在一堆中添加多个项目的便捷方法),因此您将丢失数组引用并造成内存泄漏。 **并且您使用的 array&lt;void*&gt;double 类型不兼容(void*double 类型不同)vector 没有构造函数来传递基本类型的数组元素。它应该有一个像

这样的构造函数吗
vector::vector<T>(const T v[], const size_t sz);

然后您可以使用以下命令初始化vector&lt;double&gt;

const double initial[] = { 109.8, 98.5, 87.5 };
const size_t initial_sz = sizeof initial / sizeof initial[0];
vector<double> v(initial, initial_sz);

但是你没有这个构造函数,所以你必须使用另一种方法,比如:

vector<double> v;
v.push_back(109.8);
...
v.push_back(87.5);

如果你有另一个可迭代容器,获取迭代器并将其传递给它:

vector<double> v(another_double_container.begin(), another_double_container.end());

但是double [] 数组没有这样的迭代器,因此您不能使用doubles 数组遵循这种方法。

另一个问题是你试图在你的向量中包含不同的类型,它只允许相同类型的实例。您不能(而且编译器不允许您这样做)将不同的类型存储到向量中(因为它将元素连续存储在数组中)

【讨论】:

  • 所使用的 C API 有一个需要不同类型数组的 void** 数组,并且在编译时无法知道数组类型需要是什么。你会建议什么?
  • 您为什么使用 C api 并标记您的问题(并使用)C++?您是否听说过 C++ 强类型以及模板如何帮助解决此问题,以及纯 C 的糟糕类型?这是一个 C 问题还是一个 C++?你不能将这两种类型都包装成unionclass 并让vector 参数更强大吗?你坚持你的代码有问题吗?我不得不承认,到目前为止,我不知道你想在vector 中存储什么样的数据。原以为是一堆浮点值,现在看来要存储数组了。
  • API 来自供应商,我别无选择,只能使用它。 API 需要 void** 返回不同类型的数组。我正在使用 VS2017 和 c++/cli 将尽可能多的部分包装成强类型。我封装了大部分变量的 ref 类不允许在内部使用 std::vector 变量,因此它的构造/销毁必须在调用的成员函数中进行编码。
  • @codebender,无论如何,您最好使用包装类,使用vector&lt;your_class&gt; 并在内部对正确的类型进行正确的转换,因此您只有一个失败点,而不是很多,分布在所有代码上。
猜你喜欢
  • 1970-01-01
  • 2020-11-30
  • 1970-01-01
  • 2014-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-10
相关资源
最近更新 更多