(P76)stl(四):ptr_vector实现,通过ptr_vector来避免潜在的内存泄漏
标签: (P76)stl(四):ptr_vector实现,通过ptr_vector来避免潜在的内存泄漏 JavaScript博客 51CTO博客
2023-04-04 18:23:37 115浏览
文章目录
- 1.ptr_vector实现
- 2.通过ptr_vector来避免潜在的内存泄漏
- 3.其他版本的ptr_vector的实现
1.ptr_vector实现
- eg:P75\CalculatorTest2\main.cpp
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
#include "DebugNew.h"
#include "Node.h"
class Test
{
public:
Test()
{
cout<<"Test ..."<<endl;
}
Test(const Test& other)
{
cout <<"Copy Test ..."<<endl;
}
~Test()
{
cout <<"~Test() ..."<<endl;
}
};
int main(void)
{
vector<Test> v;
Test t1;
Test t2;
Test t3;
//这3个对象会释放
//容器中的3个对象也会释放,总共调用6次析构
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
return 0;
}
- 存放的类型是Test为啥会调用拷贝构造函数?
Test t1;
v.push_back(t1); sizeof(Test)
不是因为push_back调用,才拷贝构造函数的(因为是引用,不会调用拷贝构造函数),而是push_back的时候内部分配了空间,通过operator new来分配空间,它不调用构造函数和拷贝构造函数。
接着,placement new不分配空间,调用拷贝构造函数(在原来已经分配的空间的基础上初始化一个对象)
- eg:P75\CalculatorTest2\main1.cpp
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
#include "DebugNew.h"
#include "Node.h"
class Test
{
public:
Test()
{
cout<<"Test ..."<<endl;
}
Test(const Test& other)
{
cout <<"Copy Test ..."<<endl;
}
~Test()
{
cout <<"~Test() ..."<<endl;
}
};
int main(void)
{
vector<Test*> v;
Test* t1 = new Test;
Test* t2 = new Test;
Test* t3 = new Test;
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
return 0;
}
Test* t1 = new Test;
v.push_bak(t1); sizeof(Test*)
而如果插入的是Test*类型的对象,placement new时不分配空间,也不会调用拷贝构造函数的,因为将指针插入到向量中是不会调用拷贝构造函数的(相当于初始化的是一个指针,而不是一个对象)。
插入到向量当中,每个元素的大小的sizeof(Test*),不论什么类型的指针, 插入到向量中为每个元素分类的空间都是4个字节。
sizeof(Test)插入的字节数是类的大小。
指针的初始化是不需要调用拷贝构造函数来初始化。
- 测试:因为初始化的是指针,是不会调用拷贝构造函数来初始化。
vector向量只是接管了指针本身,而并没有接管指针变量所指向的对象。
vector容器,stl的所有容器都是值语义的。
往容器中插入指针,容器拷贝的仅仅只是指针,容器并不负责指针所指向的内存空间的释放。
往容器中插入的是类对象,容器拷贝整个对象(容器中拷贝的对象与原对象脱离关系,原对象的释放由外部自己负责,容器会负责所拷贝到容器中对象的释放。) - 对于指针而言,将其值可以看出是long型的整型。类似vector这玩意会调用析构吗?它又不是对象,所以放到vector中不安全的指针(指向对象的指针是不安全的),需要自己释放
2.通过ptr_vector来避免潜在的内存泄漏
- 实现一个类模板ptr_vector指针vector
ptr_vector不仅仅释放指针本身,还用于负责指针所指向空间的释放 - eg:P75\CalculatorTest2\ptr_vector.h
- 关于派生类模板调用基类模板中的相关函数见:C++类模板中如何调用其基类模板中的函数,下面的代码因为是模板继承后,重定义了基类的成员函数,但是又想调用基类中重定义的函数的做法,参考:(P17-P18)通过using定义基础类型和函数指针别名,使用using和typedef给模板定义别名
#ifndef _PTR_VECTOR_H_
#define _PTR_VECTOR_H_
#include <vector>
#include <memory>
template<typename T>
class ptr_vector : public std::vector<T*>
{
public:
~ptr_vector()
{
clear();
}
//重定义vector中的clear方法
void clear()
{
//负责指针所指向内存空间的释放
typename std::vector<T*>::iterator it;
for (it = std::vector<T*>::begin(); it != std::vector<T*>::end(); ++it)
delete *it;//it就是T*的指针,取*就是T*
}
//基类的clear释放自指针变量本身,
//using std::vector<T*>::clear;别处使用:clear();因为是值拷贝,基类不释放无所谓,只要delte一次就行
std::vector<T*>::clear();
//将指针插入向量中时,执行operator new可能分配内存失败,即指针没有插入到向量中,
//std::vector<T*>::clear();可能不会释放内存,存在内存泄漏
//处理方法:将所有权转移到智能指针ptr对象上,即使内部内存空间分配失败。智能指针是一个局部对象
//捕捉异常时,在栈展开的时候会释放对象,将其持有的val指针释放掉
void push_back(T* const &val)
{
std::auto_ptr<T> ptr(val);
std::vector<T*>::push_back(val);
ptr.release();//程序运行到这里,说明operator new分配内存成功,ptr释放所有权
}
//增加一个接口:将一个智能指针插入到向量中
//原生的vector是不能将指针插入到向量中的,如果_Ty是智能指针类型,意味着智能指针在释放所有权的时候(裸指针、智能指针不能插入到vector)
//会更改内部的指针,将指针置为0,而这里的修饰是const,指针内部的数据成员是不能更改的
void push_back(std::auto_ptr<T>& val)
{
//这里不要使用std::vector<T*>::push_back(val.release());
//push_back()的operator new过后,且因为release()会释放所有权,造成这个指针成为野指针,没人管,造成内存泄漏
std::vector<T*>::push_back(val.get());
//插入成功后,释放所有权
val.release();//会更改智能指针内部的数据成员的,release()表示智能指针释放所有权
}
//boost也有ptr_vector,考虑的内容更多
};
#endif // _PTR_VECTOR_H_
- eg:P75\CalculatorTest2\main2.cpp
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
#include "DebugNew.h"
#include "Node.h"
#include "ptr_vector.h"
class Test
{
public:
Test()
{
cout<<"Test ..."<<endl;
}
Test(const Test& other)
{
cout <<"Copy Test ..."<<endl;
}
~Test()
{
cout <<"~Test() ..."<<endl;
}
};
int main(void)
{
// vector<Test*> v;
ptr_vector<Test> v;
Test* t1 = new Test;
Test* t2 = new Test;
Test* t3 = new Test;
//动态内存的所有权转移到了ptr_vector向量中,由向量来负责释放
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
return 0;
}
- 测试:
- eg:
智能指针auto_ptr:使用起来是一个类,类对象在其生命周期结束的时候,会调用他的析构函数,在析构函数中对其所持有的资源进行释放;
ptr_vector:防止当指针还没有插入到向量的过程中,就分配内存失败了,此时指针是没办法释放的,此外,智能指针对象是没办法插入到向量中的
P75\Calculator\Node.h
void AppendChild(std::auto_ptr<Node>& node, bool positive)
{
//存在push_back的时候,operator new失败,可能存在风险
//智能指针(原生指针)node.release()释放了所有权之后就是1个裸指针,原生指针还没有插入到向量中,operator new分配内存就出现了异常
//此时,裸指针还没有插入到向量中,即使析构函数~MultipleNode();调用了,也没办法遍历出来,将其释放掉,
//那么指针所指向的内存就出现了泄漏
//解决办法是,使用ptr_vector
// childs_.push_back(node.release());//元素类型是Node*,不能放入智能指针,而应该放入原生指针:释放所有权后传进容器中
childs_.push_back(node);//因为重载了push_back方法
positive_.push_back(positive);
}
~MultipleNode();
private:
//有很多子代,将其放入向量中
// std::vector<Node*> childs_;//vector里面存放的是Node*
ptr_vector<Node> childs_;
std::vector<bool> positive_;//节点的正负性
};
P75\Calculator\Node.cpp
MultipleNode::~MultipleNode()
{
// std::vector<Node*>::const_iterator it;
// for (it = children_.begin(); it != children_.end(); ++it)
// {
// //it实际上是Node*的指针的指针,它存放的类型是Node*,*it取出它里面存放的元素Node*
// delete *it;
// }
}
- 测试:
将析构函数注释掉,且不使用ptr_vector
MultipleNode::~MultipleNode()
{
// std::vector<Node*>::const_iterator it;
// for (it = children_.begin(); it != children_.end(); ++it)
// {
// //it实际上是Node*的指针的指针,它存放的类型是Node*,*it取出它里面存放的元素Node*
// delete *it;
// }
}
会出现内存泄漏
不再出现内存泄漏的问题了
3.其他版本的ptr_vector的实现
ptr_vector析构的时候会析构自己开辟出来的存放指针的空间,同时析构指针本身指向的空间而,一般容器不会析构指针本身指向的空间
- 参考boost::ptr_vector。
- eg:
#ifndef _PTR_VECTOR_HH
#define _PTR_VECTOR_HH
#include <vector>
#include <memory>
#include <assert.h>
namespace myself
{
#define ASSERT(If, Msg) \
if(!(If)) \
{\
fprintf(stderr, "Error/(%s, %d): %s, abort.\n", __FILE__, __LINE__, Msg); abort();\
}
template<typename T>
class ptr_vector
{
public:
typedef unsigned int size_type;
typedef std::vector<T*> vector;
typedef T* value_type;
typedef value_type& reference;
explicit ptr_vector()
{
}
//ptr_vector析构的时候会析构自己开辟出来的存放指针的空间,同时析构指针本身指向的空间
//而一般容器不会析构指针本身指向的空间
~ptr_vector()
{
clear();
}
void clear()
{
if(!m_vector.empty()) {
//typename vector::iterator it;
for(auto it = m_vector.begin(); it != m_vector.end(); ++it) {
delete *it;//释放指针指向的内存.
}
}
m_vector.clear();//释放指针本身.
}
//在尾部追加元素
//unique_ptr对象包装一个原始指针,并负责其生命周期。
//当该对象被销毁时,会在其析构函数中删除关联的原始指针,防止内存泄漏
void push_back(T* const &v)
{
ASSERT(v, "NULL point at ptr_vector push_back()");
std::unique_ptr<T> tmp(v);
m_vector.push_back(v); //使用 unique_ptr 保证push_back失败时,v也能正常释放.
tmp.release();
}
//删除最后一个元素并返回指向删除对象的指针
std::unique_ptr<T> pop_back()
{
ASSERT( !m_vector.empty(), "'pop_back()' on empty container");
std::unique_ptr<T> tmp(m_vector.back());
m_vector.pop_back();
return std::move(tmp);
}
//提供像c数组通过下表访问的方法
reference operator[](size_type n)
{
ASSERT(n < size(), "operator[] n out of the border")
return m_vector[n];
}
bool empty()
{
return m_vector.empty();
}
size_type size()
{
return m_vector.size();
}
void reserve(size_type n)
{
m_vector.reserve(n);
}
void resize(size_type s)
{
size_type size = this->size();
if(s < size) {
for(auto it = m_vector.begin() + s; it != m_vector.end(); ++it) {
delete *it;//释放指针指向的内存.
}
m_vector.resize(s);
} else if(s > size) {
for(; size != s; ++size) {
push_back(new T);
}
}
ASSERT(s == this->size(), "'resize' error size asymmetry");
}
void swap(ptr_vector<T>& v)
{
m_vector.swap(v.base());
}
private:
//将拷贝构造和赋值 设置为私有方法,防止所有权转移
ptr_vector<T>& operator=(const ptr_vector<T>&);
ptr_vector<T>(ptr_vector<T>&);
vector& base()
{
return m_vector;
}
vector m_vector;
};
}
#endif
- eg:性能测试:
#include "ptr_vector.h"
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::shared_ptr;
using std::unique_ptr;
using std::string;
using std::vector;
//https://www.bbsmax.com/A/kvJ3pKKw5g/
//https://www.codeproject.com/Articles/7351/ptr-vector-A-Container-For-Pointers
class TSomeData {
private:
int data;
public:
TSomeData(int d)
: data(d) {
// Empty
}
};
const int TEST_ITERATIONS = 10000000;
typedef vector<unique_ptr<TSomeData>> TVectorOfUnique;
typedef vector<shared_ptr<TSomeData>> TVectorOfShared;
typedef myself::ptr_vector<TSomeData> TPtrVector;
int test_cmp2sharedptr() {
clock_t start;
clock_t end;
/*C 库函数 clock_t clock(void) 返回程序执行起(一般为程序的开头),处
理器时钟所使用的时间。为了获取 CPU 所使用的秒数,您需要除以 CLOCKS_PER_SEC。*/
start = ::clock();
TVectorOfShared vectorOfShared;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test vector of shared_ptr
shared_ptr<TSomeData> data(new TSomeData(i));
vectorOfShared.push_back(data);
}
end = ::clock();
cout << "Vector of shared:\n CPU occupy Time executed: "
<< static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000))
<< endl;
// ********************************************************************* //
start = ::clock();
TVectorOfUnique vectorOfUnique;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
unique_ptr<TSomeData> data(new TSomeData(i));
vectorOfUnique.push_back(std::move(data));
}
end = ::clock();
cout << "Vector of unique:\n CPU occupy Time executed: "
<< static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000))
<< endl;
// ********************************************************************* //
start = ::clock();
TPtrVector ptrVector;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test ptr_vector
TSomeData* data = new TSomeData(i);
ptrVector.push_back(data);
}
end = ::clock();
cout << "PtrVector:\n CPU occupy Time executed: "
<< static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000))
<< endl;
return 0;
}
int test_ptr_vector() {
myself::ptr_vector<string> vec;
vec.push_back (new string ("Hello, "));
vec.push_back (new string ("world! "));
//cout << vec[0] << vec.at(1)
// << *vec.begin() << vec.begin()[1]
// << vec.front() << vec.back()
// << endl;
cout << *vec[0] << endl;
return 0;
}
int main() {
test_cmp2sharedptr();
test_ptr_vector();
return 0;
}
- 结果:-O0情况下测试结果(unique_ptr性能会好点)
- 参考:muduo ptr_vector详解
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论
您可能感兴趣的博客