一.RAII(Resource Acquisition Is Initialization)
RAII资源获取即初始化,RAII的思想就是在构造时初始化资源,或者托管已经构造的资源。在析构的时候释放资源。一般不允许复制或赋值,并且提供若干的资源访问的方法。比如:我们创建一个文件读写的类,当我们通过类创建一个栈对象时,并初始化传入要打开文件的指针,当我栈对象的生命周期结束会自动调用析构函数将文件关闭,这就为我们解决一些文件未close的安全问题。
#include <iostream>
#include <string>
#include <string.h>
using std::endl;
using std::cout;
using std::string;
class safeFile
{
private:
FILE* _fp;
public:
safeFile(FILE* fp)
:_fp(fp)
{
cout<<"safeFile(FILE* fp)"<<endl;
}
//一般不允许赋值和复制 删除或者设置成私有的
safeFile(const safeFile& rhs)=delete;
safeFile& operator=(const safeFile& rhs)=delete;
void write(const string& msg)
{
fwrite(msg.c_str(),1,strlen(msg.c_str()),_fp);
cout<<"void write(const string& msg)"<<endl;
}
~safeFile()
{
if(_fp)
{
fclose(_fp);
cout<<"~safeFile()"<<endl;
}
}
};
int main()
{
safeFile sf(fopen("test.txt","a+"));//sf 是栈对象销毁时自动执行关文件的操作
sf.write(string("hello,world"));
}
我们在简单的实现一下RAII
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
class RAII
{
private:
T* _data;
public:
RAII(T* data)
:_data(data)
{
cout<<"RAII(T* data)"<<endl;
}
~RAII()
{
cout<<"~RAII()"<<endl;
if(_data)
{
delete _data;
_data=nullptr;
}
}
T* operator->()
{
return _data;
}
T& operator*()
{
return *_data;
}
T* get()
{
return _data;
}
private:
RAII(const RAII& rhs);
RAII& operator=(const RAII& rhs);
};
class Point
{
private:
int _a;
int _b;
public:
Point(int a=0,int b=0)
:_a(a)
,_b(b)
{
cout<<"Point(int a=0,int b=0)"<<endl;
}
~Point()
{
cout<<"~Point()"<<endl;
}
void print()
{
cout<<"( "<<_a<<" , "<<_b<<" )"<<endl;
}
};
int main()
{
//pt本身不是指针,但是具备指针的功能
RAII<Point> pt(new Point(1,2));
pt.operator->()->print();
pt->print();
}
总结:这里pt本身不是指针,但他具备指针的功能,我们是用pt对象来托管new Point(1,2)这块堆空间,当pt对象的生命周期结束,自动调用析构函数,我们在将这块托管的堆空间释放。并且我们在RAII内设置一些方法可以访问这块托管的堆空间资源,这样我们就不用担心内存泄漏的问题,防止堆空间资源用完没有进行回收。
二.智能指针
在C++动态内存管理中,我们通过new在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化,通过delete接受一个动态对象的指针将该对象销毁。动态内存的使用很容易出问题,因为要确保在合适的时间释放内存是非常困难的。有时候我们会忘记释放内存,这时就会产生内存泄漏;有时在还有指针指向内存时我们就将内存释放了,这种情况会导致引用非法指针。因此为了更加容易和安全使用动态内存,标准库提供了智能指针类型来管理动态对象。
头文件<memory>
1.shared_ptr
shared_ptr既可以传左值也可以传右值,其中我们可以认为每个shared_ptr都有一个引用计数。无论何时我们拷贝一个shared_ptr引用计数都会递增。例如,当用一个shared_ptr去初始化另一个shared_ptr,或者将他作为参数传递给一个函数以及作为参数的返回值时,她所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时计数器就会递减,如果引用计数减少为0时,他就会自动释放自己所管理的类。
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::shared_ptr;
using std::string;
using std::make_shared;
using std::weak_ptr;
//与unique_ptr相反的是shared_ptr既可以传左值也可以传右值
void test()
{
string str("helloworld");
// shared_ptr<string> sp=std::make_shared<string>(new char[str.size()]());
// *sp=str;
//通过make_shared返回shared_ptr类型的指针,来对sp进行初始化
shared_ptr<string> sp=std::make_shared<string>(str);
cout<<"*sp = "<<*sp<<endl;
cout<<"-------------------------------------"<<endl;
shared_ptr<int> sp1(new int(10));
cout<<"&*sp1 = "<<&*sp1<<endl;
cout<<"*sp1 = "<<*sp1<<endl;
cout<<"sp1.get() = "<<sp1.get()<<endl;
cout<<"sp1.use_count() = "<<sp1.use_count()<<endl;
cout<<"sp1.unique() = "<<sp1.unique()<<endl;
cout<<endl;
cout<<"赋值运算符函数"<<endl;
shared_ptr<int> sp2(new int(20));
cout<<"*sp2 = "<<*sp2<<endl;
cout<<"sp2.get() = "<<sp2.get()<<endl;
sp2=sp1;
cout<<"&*sp2 = "<<&*sp2<<endl;
cout<<"*sp2 = "<<*sp2<<endl;
cout<<"sp2.get() = "<<sp2.get()<<endl;
cout<<"sp2.use_count() = "<<sp2.use_count()<<endl;
cout<<"sp2.unique() = "<<sp2.unique()<<endl;
cout<<endl;
shared_ptr<int> sp3=sp2;
cout<<"&*sp3 = "<<&*sp3<<endl;
cout<<"*sp3 = "<<*sp3<<endl;
cout<<"sp3.get() = "<<sp3.get()<<endl;
cout<<"sp3.use_count() = "<<sp3.use_count()<<endl;
cout<<"sp3.unique() = "<<sp3.unique()<<endl;
}
int main()
{
test();
return 0;
}
2.unique_ptr
与shared_ptr不同的是,某个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。并且unique_ptr也没有类似make_shared的标准库函数返回一个unique_ptr。当我们定一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化方式。因为unique_ptr没有拷贝构造函数和赋值运算符函数,但是具有移动语义(有移动构造函数和移动赋值函数)并且可以作为容器元素,但是只能传递右值。
#include <iostream>
#include <memory>
#include <vector>
using std::vector;
using std::unique_ptr;
using std::cout;
using std::endl;
void test()
{
unique_ptr<int> up(new int(2));
// unique_ptr<int> up1=up;//error 没有拷贝构造函数
// unique_ptr<int> up2;
// up2=up; //error没有赋值运算符
vector<unique_ptr<int>> vec_up;
//vec_up.push_back(up); //error 还是会调用拷贝构造函数 unique_ptr(const unique_ptr&) = delete;
vec_up.push_back(std::move(up));
vec_up.push_back(unique_ptr<int>(new int(29)));
cout<<"vec_up[0] = "<<*vec_up[0]<<endl<<"vec_up[1] = "<<*vec_up[1]<<endl;
}
int main()
{
test();
}
左右值相互转换的方法:
需要右值的时候
将左值转换为右值std::move()或构建临时对象Point(1,2)
需要左值的时候
可以使用构造函数创建对象,创建有名对象。Point pt(1,2)
Point&& rhf=Point(1,2) 利用右值引用是左值的特性
3.weak_ptr
shared_ptr有一个缺陷就是循环引用的情况例如:
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::shared_ptr;
using std::string;
using std::make_shared;
using std::weak_ptr;
class Child;
class Parent
{
public:
Parent()
{
cout<<"Parent()"<<endl;
}
~Parent()
{
cout<<"~Parent()"<<endl;
}
shared_ptr<Child> pchild;
};
class Child
{
public:
Child()
{
cout<<"Child()"<<endl;
}
~Child()
{
cout<<"~Child()"<<endl;
}
shared_ptr<Parent> pparent;
};
//shared_ptr的问题循环引用的问题
void circle_test()
{
shared_ptr<Parent> spParent(new Parent());
shared_ptr<Child> spChild(new Child());
cout<<"spParent.use_count() = "<<spParent.use_count()<<endl;
cout<<"spChild.use_count() = "<<spChild.use_count()<<endl;
//循环引用会导致内存泄露。解决方法weak_ptr和shared_ptr配合使用;其中weak_ptr不会使引用计数++
spParent->pchild=spChild; //sp=sp
spChild->pparent=spParent;
cout<<"spParent.use_count() = "<<spParent.use_count()<<endl;
cout<<"spChild.use_count() = "<<spChild.use_count()<<endl;
}
int main()
{
circle_test();
}
可以看出来此时只有构造函数没有析构函数这就会造成内存泄漏。
创建完对象时指针引用如下图所示:
当进行析构时
就会造成引用计数不为零,并且指针相互只向。
class Child;
class Parent
{
public:
Parent()
{
cout<<"Parent()"<<endl;
}
~Parent()
{
cout<<"~Parent()"<<endl;
}
weak_ptr<Child> pchild;
};
class Child
{
public:
Child()
{
cout<<"Child()"<<endl;
}
~Child()
{
cout<<"~Child()"<<endl;
}
weak_ptr<Parent> pparent;
};
当将类的数据成员变成weak_ptr时
因为weak_ptr不会使引用计数递增
此时释放时引用计数都为零可以将堆空间进行释放。
weak_ptr的一些注意事项:
- weak_ptr没有成员访问运算符->和解引用运算符*。因此不能用weak_ptr指针去访问托管的数据。
- weak_ptr的初始化
- weak_ptr<Point> wp创建空对象
- weak_ptr<Point> wp(sp)用shared_ptr的指针类型对weak_ptr进行初始化
- weak_ptr<Point> wp=sp1
- 将weak_ptr提升一个shared_ptr,因为weak_ptr不能查看管理资源内容,转化为shared_ptr shared_ptr<Point> sp4=wp.lock();
- bool flag=wp.expired();如果use_count为0则flag为true,否则use_count不为0则flag为false