个人理解:所谓智能指针就是为了防止出现内存泄露,出现野指针等操作的而定义的一种类型。
在标准库当中有两种类型的智能指针
- shared_ptr 允许多个指针指向同一个对象
- unique_ptr 独占所指向的对象
另外还有weak_ptr的伴随类和auto_ptr,指向shared_ptr所管理的对象。auto_ptr是C++98版本中的内容,C++11中已经不再使用。
头文件名为 memory
使用动态内存的原因:
- 程序不知道自己需要使用多少对象
- 程序不知道对象的准确类型
- 程序要在多个对象间共享数据
问题
c++ primer第四版 p420页上面的带有指针成员的类
(有的编译器有优化,对指针值的拷贝构造函数会考虑到悬垂指针的情况)
class HasPtr
{
public:
HasPtr(int *p,int i):ptr(p),val(i){}
int *get_ptr() const { return ptr; }
int get_int() const { return val; }
void get_ptr(int *p) { ptr=p; }
void set_int(int i) { val=i ;}
int get_ptr_val() const { return *ptr; }
void set_ptr_val(int val) const { *ptr=val; }
private:
int *ptr;
int val;
};
int main()
{
ios::sync_with_stdio(false);
int *ip=new int(42);
HasPtr ptr(ip,10);
delete ip;
ptr.set_ptr_val(0);
return 0;
}
上面的代码当中,ip和ptr中的指针指向同一个对象。删除该对象时,ptr中的指针不再指向有效对象。然而,没有办法得知对象已经不在存在了。(p421)
其中的HasPtr中的val只表示HasPtr这个类中包含除了指针类型成员以外,还包含一个指针成员,所以val的值和智能指针这部分没有联系。
定义智能指针
智能指针将保证在撤销指向对象的最后一个HasPtr对象时删除对象。为此,添加一个析构函数用来删除指针,删除标准为指向该对象最后一个指针也被撤销,那么删除该对象,以便回收内存。
方法:
引入计数器,每次创建类的新对象时,初始化指针并将计数置为1,当对象作为另一个对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。
代码中删除了val这个成员,代码来自c++ primer 第四版
#include <bits/stdc++.h>
using namespace std;
class HasPtr;
class U_Ptr
{
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p):ip(p),use(1){}
~U_Ptr(){ delete ip; }
};
class HasPtr
{
public:
HasPtr(int *p): ptr(new U_Ptr(p)){}
HasPtr(const HasPtr &orig):ptr(orig.ptr){ ++ptr->use; }
HasPtr& operator = (const HasPtr&);
int get_ptr() { return *(ptr->ip); }
int get_use() { return ptr->use; }
~HasPtr() { if(--ptr->use==0) delete ptr; }
private:
U_Ptr *ptr;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use;
if(--ptr->use==0)
delete ptr;
ptr = rhs.ptr;
return *this;
}
int main()
{
ios::sync_with_stdio(false);
int* obj=new int(1);//注意不要delete obj
HasPtr *hp1=new HasPtr(obj);
HasPtr hp2=*hp1;//赋值
cout<<hp1->get_use()<<endl;//输出2
cout<<hp2.get_use()<<endl;//输出2
delete hp1;
cout<<hp2.get_use()<<endl;//输出1
return 0;
}
上面代码的图
标准库中的智能指针
在boost库中和c++11的memory库当中都有对智能指针的实现,下面记录和整理C++11的memory库中的智能指针的简单用法。
shared_ptr类
shared_ptr操作
- use_count 返回与p共享对象的智能指针的数量
- unique 判断use_count是否为1,如果为1则返回true
- get 返回保存的指针
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
string *s=new string("abc");
shared_ptr<string> p1(s);
cout<<*p1<<endl;//解引用
cout<<p1.unique()<<endl;//是否只有一个指针指向目标内存
cout<<p1.use_count()<<endl;//输出与p1共享对象的智能指针的数量
shared_ptr<string> p2(p1);
cout<<p1.unique()<<endl;//输出0
cout<<p1.use_count()<<endl;//输出2
cout<<*(p1.get())<<endl;//返回p1中保存的指针
delete s;
return 0;
}
是用make_shared函数构造给定类型的对象。
shared_ptr<int> pi=make_shared<int>(100);
cout<<*make_shared<int>(100)<<endl;//可以直接输出试试
shared_ptr释放内存
- 当指向一个对象的最后一个shared_ptr被销毁时shared_ptr会自动销毁对象。
- 当动态对象不再被使用时,shared_ptr会自动释放动态对象。(例如在局部变量当中使用指针)
举个局部变量造成内存泄露的例子
如果程序这样写
int* P_factory(int x)
{
return new int[x];
}
void p_test(int x)
{
P_factory(x);//分配的内存在局部变量当中丢失(也就是没有指针指向这块内存,从而造成内存泄露)
}
int main()
{
ios::sync_with_stdio(false);
for(int i=1;i<=100000;i++)
p_test(10000);//boom!
return 0;
}
但是如果用了shared_ptr
shared_ptr<int> factory(int x)
{
return make_shared<int>(x);
}
void test(int x)
{
factory(x);//自动回收内存
}
int main()
{
ios::sync_with_stdio(false);
for(int i=1;i<=100000;i++)
test(10000);//什么也不会发生
return 0;
}
shared_ptr与new结合使用:
使用shared_ptr最需要注意的就是不要和内置指针(普通指针)混用以及相互关联,也不要使用get初始化另一个智能指针或者为智能指针赋值!
接受指针参数的智能指针构造函数是explict的
shared_ptr<int> clone(int p)
{
return new int(p);//错误,无法隐式转换
}
shared_ptr<int> clone(int p)
{
return shared_ptr<int>(new int(p));
}
shared_ptr<int> p1 = new int(1024);//错误
shared_ptr<int> p2(new int(1024));
reset操作:
如果当前指针的unique()值为1,那么调用reset函数重新指向一个对象时,先释放之前的内存。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
shared_ptr<int> sp(new int(100));
shared_ptr<int> sp1(sp);
cout<<sp.use_count()<<" "<<sp1.use_count()<<endl;//2 2
sp.reset(new int(10));
cout<<sp.use_count()<<" "<<sp1.use_count()<<endl;//1 1
return 0;
}
unique_ptr类
顾名思义,只有一个指针指向当前对象。unique_ptr的生命周期为从创建开始到离开作用域,如果离开作用域时未释放指针,系统会自动回收内存,释放指针。所以,unique_ptr不支持拷贝与赋值操作。
unique_ptr操作
- reset 释放指针所指向的对象
- get 获得指针
- release 放弃对指针的控制权,返回指针,并将unque_ptr置为空
reset操作
#include <iostream>
#include <memory>
int main ()
{
std::unique_ptr<int> up; // 空
up.reset (new int); // 获取指针
*up=5;
std::cout << *up << '\n';//5
up.reset (new int); // 删除原对象,重新获取新的对象
*up=10;
std::cout << *up << '\n';//10
up.reset(); // 删除对象
return 0;
}
在unique_ptr作为参数传递和返回值时可以拷贝。
#include<bits/stdc++.h>
using namespace std;
unique_ptr<int> clone(int p=0)
{
unique_ptr<int> ret(new int (p));
return ret;
}
unique_ptr<int> pass(unique_ptr<int> p)
{
*p=0;
return p;
}
int main()
{
cout<<*clone()<<endl;
unique_ptr<int> up=clone(10);
unique_ptr<int> new_p=pass(clone());
cout<<*new_p<<endl;
return 0;
}
release用法
#include <iostream>
#include <memory>
int main ()
{
std::unique_ptr<int> auto_pointer (new int);
int * manual_pointer;
*auto_pointer=10;
manual_pointer = auto_pointer.release();//auto_pointer把指向对象给manual_pointer
// auto_pointer 为空
std::cout << "manual_pointer points to " << *manual_pointer << '\n';
delete manual_pointer;
return 0;
}
weak_ptr类
指向一个shared_ptr管理的对象,将weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,而且weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。
weak_ptr主要操作
- w.reset 将w置空
- w.use_count 与w共享的shared_ptr的数量
- w.expired 如果use_count为0返回true,否则返回false
- w.lock 如果expired为1返回一个空的shared_ptr否则返回一个指向w的对象的shared_ptr
expire的例子,看了就理解了
#include <iostream>
#include <memory>
int main ()
{
std::shared_ptr<int> shared (new int(10));
std::weak_ptr<int> weak(shared);
std::cout << "1. weak " << (weak.expired()?"is":"is not") << " expired\n";
shared.reset();
std::cout << "2. weak " << (weak.expired()?"is":"is not") << " expired\n";
return 0;
}
lock的例子
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> sp1,sp2;
std::weak_ptr<int> wp;
// sharing group:
// --------------
sp1 = std::make_shared<int> (20); // sp1
wp = sp1; // sp1, wp
sp2 = wp.lock(); // sp1, wp, sp2
sp1.reset(); // wp, sp2
sp1 = wp.lock(); // sp1, wp, sp2
std::cout << "*sp1: " << *sp1 << '\n';
std::cout << "*sp2: " << *sp2 << '\n';
return 0;
}
上面的几个例子来自c plus plus,简单明了 直接拿来用了
有关动态内存分配
内存耗尽
使用new表达式申请内存时,如果内存耗尽,会抛出bad_alloc的异常
使用定位new的方式可以在内存分配失败时返回一个空指针,相当于向new表达式传递了一个参数
int *p2=new (nothrow) int(1);
释放内存
返回一个指向动态内存的指针时,要手动释放
如果不加上delete p会爆内存
int* fun()
{
return new int[1000];
}
int main()
{
for(int i=0;i<1000000;i++)
{
int *p=fun();
// delete [] p;
}
return 0;
}
使用指向动态内存的局部变量指针在内释放的情况。如果该指针在离开作用域之前未释放,会造成内存泄露
int* fun()
{
return new int[1000];
}
void use()
{
int *p=fun();
}
悬垂指针
使用被释放的指针
int main()
{
int *p=new int[10];
for(int i=0;i<10;i++)
*(p+i)=i;
delete [] p;
for(int i=0;i<10;i++)
cout<<*(p+i)<<endl;//使用被释放的指针
return 0;
}
多个指针指向同一块内存
书上的例子
int *p(new int(42));
auto q=p;
delete p;
p=nullptr;
上面的q仍然指向被释放的内存
shared_ptr和unique_ptr指向数组
定义方式
unique_ptr<int[]> up(new int[10]);
当up销毁管理内存时,自动delete[]
当一个unique_ptr指向一个数组而不是单个对象时,不能使用点和箭头。可以使用下表运算符
unique_ptr<int[]> up(new int[10]);
for(size_t i = 0;i!=3;i++)
up[i]=i;
shared_ptr不支持动态数组,如果想要使用shared_ptr管理动态数组,需要自己定义删除器
shared_ptr<int> sp(new int[10], [](int *p){ delete[] p;});
使用lambda表达式作为删除器
由于shared_ptr没有下表运算符,所以可以使用内置指针
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
shared_ptr<int> sp(new int[10], [](int *p){ delete[] p;});
for(size_t i=0;i!=10;i++)
*(sp.get()+i)=i;
for(size_t i=0;i!=10;i++)
cout<<*(sp.get()+i)<<endl;
sp.reset();
return 0;
}
练习题12.23
#include<bits/stdc++.h>
using namespace std;
unique_ptr<char[]> connect_str(char *a,char *b)
{
int la=strlen(a);
int lb=strlen(b);
unique_ptr<char[]> up(new char[la+lb]);
for(size_t i=0;i!=la;i++)
up[i]=a[i];
for(size_t i=strlen(a);i!=la+lb;i++)
up[i]=b[i-la];
up[la+lb]='\0';
return up;
}
int main()
{
ios::sync_with_stdio(false);
char a[]={"abc"};
char b[]={"cde"};
unique_ptr<char[]> uc=connect_str(a,b);
for(size_t i=0;uc[i];i++)
cout<<uc[i]<<endl;
return 0;
}
to be continue~