关于c++容器vector,push_back带有资源管理(new,malloc等)对象时需要注意的事情。拷贝,析构,作用域

本文探讨了C++标准库vector在push_back任务类对象时的潜在陷阱,特别是在资源管理中可能导致的double-free问题。通过实例代码和分析,强调了在处理涉及资源的类时,如何避免拷贝和析构带来的复杂性,以及使用vector<task_class*>替代的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于c++标准库中的vector,我相信应该是用的比较多的一种结构。读者们对此大多应该也不陌生,但是对于初学者可能仍有一些值得注意的地方,本篇文章将讨论其中一种陷阱。

我们都知道C++类的拷贝有最简单的两种区分:深拷贝和浅拷贝。当我们使用vector进行push_back类的对象时候,对于类的拷贝和析构就应该多加注意。push_back的函数原型为push_back(const value_type& __x),使用的引用,按理来说不会产生拷贝,但是当我们进一步查看push_back时候,会发现有一句语句_M_realloc_insert(end(), __x)。

原来,当我们在使用vector的方法push_back时候,首先会检查容量是否满足,当vector的容量不能满足的时候,会调用_M_realloc_insert(end(), __x),首先将原有数据进行拷贝,增加新的数据,再将原有数据进行析构。因此整个过程会产生类对象的拷贝和析构。(此处的原理不再多做描述,有兴趣的读者可进行搜查)。

当我们push_back的对象不涉及资源(如new,malloc的内存空间,文件描述符等)管理的时候,push_back对原有数据的拷贝和析构看起来无关大雅。但是一但当push_back的对象涉及到了资源管理时,push_back对原有数据的拷贝和析构就会产生很大问题。

话不多说,直接上代码,作为程序员,还是代码能给我们更多信息。头文件如下

#ifndef TEST_H

#define TEST_H

#include <vector>

using namespace std;

/*工具类,其中涉及到了最普遍的资源管理:new*/

class tool_class

{

private:

    int* pint;

public:

    tool_class();

    ~tool_class();

    void init(int value);

    void r_printf();

};

/*任务类,具有工具类tool_class成员变量new_app_class*/

class task_class

{

private:

    tool_class new_app_class;

public:

    void init(int value);

    void exe();

};

/*任务管理类,具有任务数组vector<task_class> v_arrary,存储任务*/

class management_class

{

private:

    vector<task_class> v_arrary;

public:

    management_class();

    void init();

    void exe();

};

#endif

源文件代码:

#include <iostream>

#include <string>

#include <tr1/memory>

#include <vector>

#include "test_vector.h"

/*工具类的方法定义*/

tool_class::tool_class()

{

    pint = NULL;

}

tool_class::~tool_class()

{

    if(pint != NULL)

    delete pint;

    pint = NULL;

}

void tool_class::init(int value)

{

    pint = new int;

    *pint = value;

}

void tool_class::r_printf()

{

    cout << "r_printf value start"<< endl;

    cout << "*pint = "<<*pint<< endl;

}



 

/*任务类的方法*/

void task_class::init(int value)

{

    new_app_class.init(value);

}

void task_class::exe()

{

    new_app_class.r_printf();

}


 

/*管理类的方法*/

management_class::management_class()

{

    v_arrary.clear();

}

void management_class::init()

{

    //for(int i = 0; i < 3; i++)

    //{

        task_class tmp;

        tmp.init(3);

        v_arrary.push_back(tmp);

    //}

}

void management_class::exe()

{

    for(unsigned int i = 0; i < v_arrary.size(); i++)

    {

        v_arrary[i].exe();

    }

}

int main()

{

    management_class management;

    management.init();

    management.exe();

    return 0;

}

管理类对象中我向vector中push_back了一个对象,运行结果如下:

究其原因是,push_back会进行拷贝,使用的是默认的拷贝函数。当定义的局部类对象 task_class tmp;离开作用域时候,会调用析构函数,将new的空间释放。而此时vector中任务类的工具变量任然指向这个空间,当vector离开作用域时候,会再次调用析构释放,造成free double。

而当push_back多个对象时候,情况更加难以想象(读者验证的话,可以在工具类的析构中加打印,同时删除delete,方便查看调用了多少次析构。不然会因free double中断,无法打印真实的析构调用次数)。局部中间变量对像离开作用域的析构,push_back造成的析构,都会给资源管理造成很多麻烦,尤其是当类在析构函数中释放资源时候。

而解决这种麻烦的其中一种方法是,不将类对象push_back进vector,而是将对象的指针保存进vector。vector<task_class *>的形式,其中保存的指针是由new或者malloc开辟的空间,这样数据将在手动free或delete释放之前永久有效。

而push_back造成的拷贝和析构也只是针对保存的对象指针,不涉及到指针指向的内容,就可以保证资源只由代码作者本身手动释放。

当工程代码繁多,应当小心的在类的成员变量中使用其他类。一但类的相关性牵扯较多,代码出现资源管理问题时候就很难定位。上述的方法虽然解决了问题,但是资源的开辟和释放时间之间的差距由任务的量决定,这也容易对代码的性能造成影响。

注:

文章中有错误的地方,欢迎评论区指正。

### C++vector 的源代码实现 C++ 中的 `std::vector` 是标准模板库(STL)中的一个动态数组容器。它提供了一种可以自动调整大小的数组结,允许在尾部高效地插入和删除元素。以下是基于引用内容对 `std::vector` 源代码实现的关键部分进行详细解。 #### 1. 内存分配与管理 `std::vector` 的核心功能之一是动态内存管理。它通过三个指针来跟踪其内部状态: - `_start`:指向已使用区域的起始位置。 - `_finish`:指向已使用区域的结束位置(即最后一个有效元素的下一个位置)。 - `_end_of_storage`:指向已分配但未使用的内存区域的结束位置。 当需要扩展容量,`std::vector` 会重新分配更大的内存块,并将原有数据拷贝到新内存中。例如,在插入操作中,如果当前容量不足,则会触发扩容逻辑[^2]。 ```cpp void insert(iterator pos, const T& x) { assert(pos <= _finish && pos >= _start); // 检查迭代器是否合法 if (_finish == _end_of_storage) { // 如果容量不足 size_t len = pos - _start; // 计算pos相对于_start的距离 reserve(capacity() == 0 ? 4 : capacity() * 2); // 扩容 pos = _start + len; // 调整pos的位置 } memmove(pos + 1, pos, sizeof(T) * (_finish - pos)); // 移动后续元素 *pos = x; // 插入新元素 ++_finish; // 更新_finish指针 } ``` #### 2. 造函数 `std::vector` 提供了多种造方式,包括默认造、拷贝造和移动造。其中,拷贝造函数实现了深拷贝,确保两个对象的内存独立[^3]。 ```cpp // 拷贝造函数 MyVector(const MyVector& V1) { cout << this << endl; iterator start = (_Ty*)malloc(sizeof(_Ty) * V1.capacity()); if (start == nullptr) { std::cout << "malloc error" << std::endl; exit(1); } iterator finish = start; iterator p = V1._M_start; _M_start = start; while (p != V1._M_finish) { new(finish) _Ty(*p); // 使用placement new造元素 ++p; ++finish; } _M_finish = finish; _M_end_of_storage = _M_start + V1.capacity(); } ``` 需要注意的是,对于自定义类型的元素,简单赋值会导致浅拷贝问题。因此,必须使用 placement new 来显式调用造函数[^4]。 #### 3. 插入与删除 `std::vector` 支持在尾部高效插入元素(间复杂度为 O(1)),但在中间位置插入或删除元素,需要移动大量元素,导致性能下降(间复杂度为 O(n))。 ```cpp // 尾部插入 void push_back(const T& x) { if (_finish == _end_of_storage) { // 容量不足扩容 reserve(capacity() == 0 ? 4 : capacity() * 2); } new(_finish) T(x); // 使用placement new造元素 ++_finish; } ``` #### 4. 容量管理 `std::vector` 提供了 `capacity()` 和 `reserve()` 方法来管理内部缓冲区的大小。`reserve()` 方法用于预先分配足够的内存以避免频繁的扩容操作。 ```cpp void reserve(size_t n) { if (n > capacity()) { size_t old_capacity = capacity(); size_t new_capacity = n; T* new_start = (T*)malloc(sizeof(T) * new_capacity); if (new_start == nullptr) { std::cout << "malloc error" << std::endl; exit(1); } for (size_t i = 0; i < old_capacity; ++i) { new(new_start + i) T(std::move(*(_start + i))); // 移动造 } for (size_t i = 0; i < old_capacity; ++i) { (*(_start + i)).~T(); // 显式调用函数 } free(_start); _start = new_start; _finish = _start + old_capacity; _end_of_storage = _start + new_capacity; } } ``` #### 5. 迭代器支持 `std::vector` 提供了随机访问迭代器,允许用户以类似数组的方式访问元素。 ```cpp iterator begin() { return _start; } iterator end() { return _finish; } ``` --- ### 示例程序验证 以下是一个简单的测试程序,展示了如何使用 `std::vector` 的基本功能: ```cpp #include <iostream> #include <vector> using namespace std; void test_vector() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); // 迭代器访问 vector<int>::iterator it = v1.begin(); while (it != v1.end()) { cout << *it << " "; ++it; } cout << endl; // 范围for访问 for (auto e : v1) { cout << e << " "; } cout << endl; // 下标+[]访问 for (size_t i = 0; i < v1.size(); ++i) { cout << v1[i] << " "; } cout << endl; } int main() { test_vector(); return 0; } ``` --- ### 总结 `std::vector` 的实现涉及动态内存管理、深拷贝机制以及高效的插入和删除操作。其设计充分考虑了性能与灵活性,适用于大多数需要动态数组的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值