一 unique_ptr
独占式指针(专属所有权):同一个时刻,只能由一个unique_ptr指向这个对象,当指针销毁的时候,指向的对象也销毁。
1.1 初始化
手动初始化: unique_ptr p;或者unique_ptr p(new int(5)); std:make_unique函数(c++14) 注:生成的指针不支持指定删除器语法。
1.2 常用功能
#include <iostream> #include <vector> using namespace std; class A { public: int m_n; public: A() { cout << "A的构造函数" << endl; } A(int n) { cout << "A的有参构造函数" << endl; this->m_n = n; } ~A() { cout << "A的析构函数" << endl; } }; //void Mydelete(A *pa) int Mydelete(A *pa) { delete[]pa; return 0; } int main(void) { #if 0 unique_ptr<int> p(new int(5)); //可以指定删除器 //unique_ptr<int> p2(p); //p开辟的堆空间只能p使用,p2无法使用 //auto p2 = make_unique<int>(6); //不可以指定删除器 //常用功能: //unique_ptr<A> pa(new A[3]); //申请一个数组,释放也需要一个数组 //unique_ptr<A[]> pa(new A[3]); //这种方法可以删除 //指定删除器: //using p_delete = void(*)(A*); using p_delete = int(*)(A*); //shared_ptr<A, p_delete> pa(new A[3], Mydelete); //指定删除其默认是 void(*)(T *) //shared_ptr<A> pa(new A[3], Mydelete); //指定删除其默认是 void(*)(T *) //unique_ptr<A, p_delete> pa(new A[3], Mydelete); //unique_ptr<A> pa(new A[3], Mydelete); //需要指定删除器的类型 //share_ptr和unique_ptr的指定删除器的区别 //区别1: // shared_ptr不能指定删除器的类型,unique_ptr可以 //区别2: //同类型的shared_ptr可以拥有不同的删除器,那么这些shared_ptr仍然是相同类型 //指定不同删除器会导致不同类型的unique_ptr,因为unique_ptr的尺寸会发生变化 //尺寸:unique_ptr的尺寸大小等于裸指针的大小,但是会受到删除器的影响 shared_ptr<A> p1(new A(5)); shared_ptr<A> p2(new A[5], Mydelete); vector<shared_ptr<A>> vp; vp.push_back(p1); vp.push_back(p2); unique_ptr<A> p3(new A(5)); unique_ptr<A, p_delete> p4(new A[5], Mydelete); //vector<unique_ptr<A>> v2; //v2.push_back(p3); //unique_ptr是独享指针,这块堆区不允许其它东西指向(不支持拷贝) cout << sizeof(p3) << endl; cout << sizeof(p4) << endl;//指定不同的删除器会导致不同类型的unique_ptr,因为unique_ptr的尺寸会发生变化 #endif //release():放弃智能指针的控制权,将该指针置为nullptr,返回的是裸指针 unique_ptr<int> p1(new int(5)); int *temp = p1.release(); //temp指向堆区(刚刚开辟的) if (p1 == nullptr) { cout << "p1 is nullptr" << endl; } cout << *temp << endl; unique_ptr<int> p2(temp); //使用裸指针构造一个独享指针 //裸指针是无法主动释放的(不像强引用) //reset():reset(参数) unique_ptr<int> p3(new int(6)); p2.reset(); //无参:若该智能指针独占某个对象,则释放该对象,并将p2置为nullptr p2.reset(new int(6));//有参:若该智能指针独占某个对象,则释放该对象,并将p2指向新的对象 //解引用:get():有些函数参数是裸指针不是智能指针,所以需要将智能指针转为裸指针 return 0; }
二 内存管理2
2.0
A *pa = new A;//有无构造函数,初始化为垃圾值 A *pa = new A(); //无构造函数初始化为0,有构造函数为垃圾值
2.1 new的实际分配
2.2 placement new
2.2.1 原型
void * operator new(size_t,void *p){return p};
2.2.2 作用
创建对象,但是不分配内存,而是在已有的内存块上面创建对象,用于需要反复创建并删除的对象上,可以降低分配释放内存的性能的消耗。 比如: 硬件编程: 如果知道了硬件设备的地址,想要将一个硬件设备与一个c++类直接关联,那么使用定位new非常有效 实现基础库: 基础库一般为了效率会预先开辟空间,然后在已经开辟好的空间上执行构造,几乎所有的stl容器都使用了定位new。
2.2.3 实例
#include <iostream> #include <vector> using namespace std; class A { public: int m_n; public: A() { cout << "A的构造函数" << endl; } A(int n) { cout << "A的有参构造函数" << endl; this->m_n = n; } void * operator new(size_t size, void *p) //placement new { cout << "void * operator new(size_t size, void *p)" << endl; return p; //不去分配额外的空间,直接使用已有的空间 } void *operator new(size_t size, int num1, int num2) { cout << "void *operator new(size_t size, int num1, int num2)" << endl; void *p = malloc(100); return p; } ~A() { cout << "A的析构函数" << endl; } }; int main(void) { void *p = (char *)new char[sizeof(A)]; cout << p << endl; //在p空间上给A构造 //placement new A *pa = new(p) A(); //pa->operator new(sizeof(A),p); pa->m_n = 5; cout << *(int *)p << endl; /* void *p1 = (void *)0x12345678; A *pb = new (p1) A(); pb->m_n = 1;*/ A *p2 = new(12, 13) A(); return 0; }
注意:有placement new,但是没有placement delete
2.3 内存池(v1.0)
2.3.1 起因:
malloc:内存浪费,频繁分配小块内存,则浪费的更加明显
2.3.2 作用:
减少malloc的次数,意味着减少内存的浪费以及提升了效率。
2.3.3 原理:
用malloc申请一大块内存,当要分配的时候,从这一大块内存中一点一点的分配,当一大块内存分配的差不多的时候, 再用malloc再申请一大块内存,然后再一点一点分配使用。
2.3.4 内存池的实现-版本1
问题:next指针占用8个字节,每块内存都存在next指针,导致空间的房费
#include <iostream> #include <vector> #include <ctime> using namespace std; namespace _nmsp1 { #define MYMENPOOL 1 class A { public: static void *operator new(size_t size); static void operator delete(void *phead); static int m_icout; //分配计数统计,每NEW一次,就统计一次 static int m_iMallocCount; //每malloc一次,就统计一次 private: A *next; static A* m_FreePosi; //总是指向一块可以分配出去的内存的首地址 static int m_STrunkCount;//一次分配多少倍的该内存 }; A* A::m_FreePosi = nullptr; int A::m_STrunkCount = 5; //一次分配5倍的该内存作为内存池的大小 int A::m_icout = 0; int A::m_iMallocCount = 0; void *A::operator new(size_t size) { #ifdef MYMENPOOL A* p = (A*)malloc(size); return p; #endif // MYMENPOOL A *tmplink; if (m_FreePosi == nullptr) //m_freeposi:头指针,指向空,说明没有空间了 { size_t realsize = m_STrunkCount * size; m_FreePosi = reinterpret_cast<A*>(new char[realsize]); tmplink = m_FreePosi; //把分配出来的一大块内存(5小块),彼此要链接起来,后续使用 for (; tmplink != &m_FreePosi[m_STrunkCount - 1]; tmplink++) { tmplink->next = tmplink + 1; } tmplink->next = nullptr; ++m_iMallocCount; } tmplink = m_FreePosi; m_FreePosi = m_FreePosi->next; ++m_icout; return tmplink; } void A::operator delete(void *phead) { #ifdef MYMENPOOL free(phead); return; #endif // MYMENPOOL (static_cast<A*>(phead))->next = m_FreePosi; //为了让下一次知道这个地址的next指向是谁,必然是下一个空置,所以将本次m_FreePosi,变成地址的next m_FreePosi = static_cast<A*>(phead); //告诉下次的new的,这个地址已经是空着了,直接进行数据覆盖 } void func() { clock_t start, end; start = clock(); for (int i = 0; i < 1500; i++) { A *pa = new A(); printf("%p\n",pa); } end = clock(); cout<<"申请分配内存的次数:"<<A::m_icout<<" 内存池申请的个数:"<<A::m_iMallocCount<<" 用时(毫秒):"<<end-start<<endl; } } int main(void) { _nmsp1::func(); return 0; }
2.4 内存池(v2.0)
2.4.1 嵌入式指针
#include <iostream> using namespace std; class Test { public: int m_a; int m_b; int m_c; public: struct obj //结构体 声明 { struct obj *next; //这个next就是嵌入式指针 }; }; int main(void) { Test t1,t2; t1.m_a = 100; t1.m_b = 200; cout << "&t1" << &t1 << endl; cout << "&t2" <<&t2<< endl; Test::obj *pt; //定义一个指针 pt = (Test::obj*)&t1; //pt这时就有了t1的地址了 Test::obj *pt2; pt2 = (Test::obj *)&t2; //尾插法一样,pt2就像一个新过来的 pt->next = pt2; //上面这一步,就是原本最后一个的next,指向这个新来的,前后实现连接 pt = pt->next; //然后将pt移到最后一个 cout << "&pt->next" << &pt->next << endl; cout << sizeof(t1) << endl; return 0; }
2.4.2 工作原理
借用A对象所占用的内存空间的前8个字节,这8个字节用来链住这些空闲的内存块。 一旦某一块被分配出去,那么这个块的前8个字节就不再需要 此时这八个字节就可以正常使用,成功使用嵌入式指针有个前提条件 sizeof()超过8个字节的类就可以安全的使用嵌入式指针
2.4.3 使用嵌入式指针改进内存池
#include <iostream> using namespace std; //专门的内存池类 class myAllocator //必须保证应用本类的sizeof()不少于8个字节,否则会崩溃或者报错。 { public: //分配内存的接口 void *Allocate(size_t size) { obj *tmplink; if (m_FreePosi == nullptr) { //为空,申请一个内存池,要申请一大块内存 size_t realSize = m_sTruckCount * size; //申请m_sTruckCount倍的内存 m_FreePosi = (obj *)malloc(realSize); tmplink = m_FreePosi; //把分配出来的一大块内存(5小块),彼此链接起来 for (int i = 0; i < m_sTruckCount - 1; i++) { tmplink->next = (obj*)((char *)tmplink + size); tmplink = tmplink->next; } tmplink->next = nullptr; } tmplink = m_FreePosi; m_FreePosi = m_FreePosi->next; return tmplink; } //释放内存接口 void deallocate(void *phead) { ((obj*)phead)->next = m_FreePosi; m_FreePosi = (obj *)phead; } private: //写在类内的结构,这样只让其在类内使用 struct obj { struct obj *next; //嵌入式指针 }; int m_sTruckCount = 5; //一次分配5倍的该类内存作为内存池的大小 obj *m_FreePosi = nullptr; }; #define DECLARE_POOL_ALLOC()\ public:\ static myAllocator myalloc;\ static void *operator new(size_t size)\ {\ return myalloc.Allocate(size);\ }\ static void operator delete(void *phead)\ {\ return myalloc.deallocate(phead);\ }\ //------------- #define INPUTENENT_POOL_ALLOC(classname)\ myAllocator classname::myalloc; class A { DECLARE_POOL_ALLOC() public: int m_i; int m_j; }; INPUTENENT_POOL_ALLOC(A) void func() { A *pa[100]; for (int i = 0; i < 15; ++i) { pa[i] = new A(); pa[i]->m_i = 12; pa[i]->m_j = 14; printf("%p\n", pa[i]); } } int main(void) { func(); }