智能指针
功能是帮助开发人员管理指针,从而避免开发者忘记释放内存从而导致出现内存泄漏问题。本身是一个类设计,当程序超过类的作用域范围,会调用自身析构函数,释放对应的资源。
<1>auto_ptr:
特点:c++98设计,采用所有权模式,c++11已经摒弃,存在潜在的内存崩溃问题
示例:
auto_ptr<string> p1 = new string("this is string");
auto_ptr<string> p2;
p2 = p1; // 编译不报错,此时p1已经失去了对string的控制权,指向为空,如果程序在继续直接使用,会出现内存崩溃现象。
<2>unique_ptr:
特点:顾名思义,独一无二,也是采用的所有权模式,内置禁止拷贝操作,但也可以进行控制权转移:std::move该方法,控制权一经移交,自身控制权失效,无法在访问对应资源,如果强行访问会跟auto_ptr发生同样的潜在的内存崩溃问题。但是内置提供get方法,在访问资源前,可通过get方法进行判空处理.
示例:
unique_ptr<string> up1(new string("hello"))
unique_ptr<string> up2(up1); // 编译报错
unique_ptr<string> up3 = up1;// 编译报错
unique_ptr<string> up4 = std::move(up1); // 控制权移交up4,自身up1控制权失效
// get方法判空
if (up1.get() != nil)
{
//再处理具体业务...
}
<3>shared_ptr:
特点:同样顾名思义,共享智能指针,意思就是说,针对某一具体对象指针,使用该智能指针可以进行共享该对象指针,等价于大家共同持有控制权。另外该智能指针采用的是引用计数的方式判别何时释放管理的对象指针。
内置方法:
use_count方法,获取当前对象指针的引用计数。为0时,资源就会被释放。
unique:返回是否是独占所有权( use_count 为 1)
release方法,当前指针会释放控制权,并且引用计数减-。
swap:交换两个shared_ptr对象(交换所控制的对象)
reset:放弃内部对象的所有权,or对象的变更,并切会使引用计数减1
get:返回控制的对象指针,因冲在了(),是否调用.get都可以。
示例:
shared_ptr<string> sp(new string("333"));
sp <=> sp.get() // 等价关系
演示:
shared_ptr<string> sp1(new string("ddd"));
shared_ptr<string> sp2(sp1);
shared_ptr<string> sp3(sp2);
cout<<sp1.use_count()<<endl; // 3
cout<<sp2.use_count()<<endl; // 3
cout<<sp3.use_count()<<endl; // 3
cout<<sp1.unique()<<endl; // 0 ,非独占,只有use_count()==1,才是独占
cout<<" sp1.reset()-----------"<<endl;
sp1.reset(); // sp1释放控制权,并使引用计数减1,当引用引用计数为0时,会调用释放对象资源而调用析构函数。
/*
shared_ptr<string> ps(new string("one"));
ps.reset(new string("second")); // 会生成一个新对象,并且触发one的引用计数减1,引用计数为0,调用string("one")析构函数释放资源,并将新生成的对象的指针控制权交给ps。
*/
cout<<sp1.use_count()<<endl; // 0
cout<<sp2.use_count()<<endl; // 2
cout<<sp3.use_count()<<endl; // 2
cout<<"sq4 add----- "<<endl;
shared_ptr<string> sp4(sp2);
cout<<sp1.use_count()<<endl; // 0
cout<<sp2.use_count()<<endl; // 3
cout<<sp3.use_count()<<endl; // 3
cout<<sp4.use_count()<<endl; // 3
cout<<"sp5.swap(sp6)"<<endl;
shared_ptr<string> sp5(new string("www"));
shared_ptr<string> sp6(new string("mmm"));
cout<<sp2.get()<<endl; // 0x755010
cout<<sp3.get()<<endl; // 0x755010
cout<<"++++"<<endl;
cout<<sp5.get()<<endl; // 0x1d80080
cout<<sp6.get()<<endl; // 0x1d800f0
sp5.swap(sp6); // 交换控制权
cout<<sp5.get()<<endl; // 0x1d800f0
cout<<sp6.get()<<endl; // 0x1d80080
!!!注:shared_ptr对数组对像管理不支持,如果使用shared_ptr管理对象数组,则需要自定义删除器。否则直接使用 std::shared_ptr<Base> pArry(new Base[5]);在释放对象调用析构函数的时候,发现仅仅调用了Base[0]对象的析构,其他的并未调用。
查看源码发现,在shared_ptr内部存储的是对象数组的原始指针,而在引用计数为0时,调用析构释放仅仅是放了Base[0]对象资源.源码如下:
所以,正确使用方式:
// 正确用法,自定义删除器
std::shared_ptr<Base> pArry(new Base[5],[](Base *ptr){delete []ptr;});
自定义删除器,源代码调用过程:
创建对象:
释放对象:
<4>weak_ptr:
特点:通过shared_ptr来构造智能指针,并且不会改变引用计数。
作用:,主要是解决shared_ptr的相互引用的缺陷造成的内存泄漏
内置方法:
use_count() 可以观测资源的引用计数。
expired() 的功能等价于 use_count()==0,但更快,表示被观测的资源(也就是 shared_ptr 管理的资源)已经不复存在。
weak_ptr 可以使用一个非常重要的成员函数lock()从被观测的 shared_ptr 获得一个可用的 shared_ptr 管理的对象, 从而操作资源。但当 expired()==true 的时候,lock() 函数将返回一个存储空指针的 shared_ptr。
基本用法总结如下:
weak_ptr<T> w; //创建空 weak_ptr,可以指向类型为 T 的对象
weak_ptr<T> w(sp); //与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型
w=p; //p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w.reset(); //将 w 置空
w.use_count(); //返回与 w 共享对象的 shared_ptr 的数量
w.expired(); //若 w.use_count() 为 0,返回 true,否则返回 false
w.lock(); //如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr
>>>shared_ptr配合weak_ptr避免循环引用的示例:
#include <iostream>
#include <memory>
using namespace std;
class Base;
class Derived;
class Base {
public:
Base() = default;
~Base() {
std::cout << "in ~Base" << std::endl;
}
void fun(){
std::cout<<"Base fun"<<std::endl;
}
std::shared_ptr<Derived> derived_;
};
class Derived {
public:
Derived() = default;
~Derived() {
std::cout << "in ~Derived" << std::endl;
}
// std::shared_ptr<Base> base_; // 出现循环引用,导致内存泄漏
std::weak_ptr<Base> base_; // 避免出现循环引用,跟shared_ptr一并食用,如果想调用成员对象的成员方法,可以使用如下构造一个shared_ptr对象来调用.
};
int main() {
std::shared_ptr<Base> ptrBase = std::make_shared<Base>();
std::shared_ptr<Derived> ptrDerived = std::make_shared<Derived>();
ptrBase->derived_ = ptrDerived;
ptrDerived->base_ = ptrBase;
std::cout <<"Base use_count: " << ptrBase.use_count() << std::endl;
std::cout <<"Derived use_count: " << ptrDerived.use_count() << std::endl;
// 使用weak_ptr创建一个shared_ptr,调用管理的指针对象的方法
auto pDerivedBase = ptrDerived->base_.lock();
pDerivedBase->fun();
// ptrDerived->base_->fun(); // ‘->’的基操作数具有非指针类型‘std::weak_ptr<Base>’
return 0;
}
// std::shared_ptr<Base> base_; // 出现循环引用,导致内存泄漏,如下引用计数
// Base use_count: 2
// Derived use_count: 2
// std::weak_ptr<Base> base_;
// Base use_count: 1
// Derived use_count: 2
// in ~Base
// in ~Derived
// Base use_count: 1
// Derived use_count: 2
// Base fun
// in ~Base
// in ~Derived
shared_ptr:多线程是否线程安全呢?
针对shared_ptr直接结论如下:
a.引用计数
引用计数底层是采用的原子操作,所以引用计数是线程安全的
b.shared_ptr修改指向
针对同一对象,shared_ptr,多线程同时读是线程安全的。但是多线程同时读写是非线程安全的。
例如:
void set(std::shared_ptr<int> & p) {
...
p = x;
}
如上如果是多线程访问,当执行p=x,引用计数会+1,如果此时刚好在赋值前,另外一个线程刚好引用计数-1等于0,导致x对象被释放了。之后又进行了p=x,此时因为x已经被释放,再使用的话,会导致断错误。
+++>>>忌混乱使用智能指针
a.裸指针跟智能指针混用
void fun(){
auto ptr = new int;
std::shared_ptr<int> sp(ptr);
delete ptr;
}
如上代码会导致重复释放,已经释放的内存,从而导致断错误。
b.禁止管理同一个裸指针
void fun(){
auto ptr = new int;
std::shared_ptr<int> sp(ptr);
std::unique_ptr<int> up(ptr);
}
如上代码同样,会导致重复释放同一块已经释放内存,导致断错误。
c.智能指针混用
void fun()
{
std::unique_ptr<int> up(new int);
std::shared_ptr<int> sp(up.get());
}
同样一个对象同时被两个智能指针管理,结束时,产生二次释放已经释放的内存,造成断错误。
注意:如下方式,在STL中是支持的:
如下代码,up将指挥权完全移交给sp,之后对象就交给sp智能指针全权管理了。
void fun()
{
std::unique_ptr<int> up(new int);
std::shared_ptr<int> sp(std::move(up));
}
d.避免管理this指针。
class A{
private:
void set(){
std::shared_ptr<A> sp(this);
}
};
如上代码,事情况而定,当A在栈上分配, 会出现断错误。但是如果分配对象在堆上,那么就是合理的。
e.只管理堆上的对象
void fun(){
A a;
std::shared_ptr<A> sp(&a);
}
当出了函数作用域,a对象释放,会调用一次delete释放内存。然而sp在作用域结束的时候,因为sp内部存储的是原始指针,仍旧会在调用一次delete释放内存,从而导致一块内存释放了两次。发生断错误。