文章目录
智能指针的引入
在传统 C++ 里我们只好使用 new 和 delete 去『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。
利用C++中一个对象出了其作用域会被自动析构(编译器),因此我们只需要在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间,这样,就减轻了程序员在编码过程中,考虑资源释放的问题,这就是RAII。
RAII,完整的英文是 Resource Acquisition Is Initialization,是 C++ 所特有的资源管理方式。
具体而言,C11的stl中为大家带来了3种智能指针,正确合理的使用可以有效的帮助大家管理资源,当然,在C++的使用智能指针没有像Java,python这种具备垃圾回收机制的语言那么舒适,毕竟,程序员还需要做一些额外的事情,但是,这远比传统的C或者C++更加优雅。
#include "pch.h"
#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class CTest {
public:
CTest() {
std::cout << "CTest()" << endl;
//在构造中完成资源的初始化。
m_pInt = new int;
}
~CTest() {
std::cout << "~CTest()" << endl;
if (m_pInt != nullptr) {
//在析构中完成资源的释放。
}
}
private:
int* m_pInt;
};
CTest test;
int main() {
int * p = new int;
return 0;
}
3种智能指针分别是:
std::shared_ptr 强指针
std::unique_ptr
std::weak_ptr 弱指针
在早期有一个auto_ptr,这四种指针在使用上有区别:
auto_ptr有缺陷是过时的产物。
uniqueptr对autoptr的问题进行了修正。
sharedptr使用了引用计数,但是会出现循环引用的问题需要配合后面的weakptr一起使用。
引用计数
深拷贝优缺点:
- 优点:每一个的对象(哪怕是通过拷贝构造函数实例化的对象)的指针都有指向的内存空间,而不是共享,所以在对象析构的时候就不存在重复释放或内存泄露的问题了。
- 缺点:内存开销大
浅拷贝优缺点:
- 优点:通过拷贝构造函数实例化的对象的指针数据变量指向的共享的内存空间,因此内存开销较小。
- 缺点:对象析构的时候就可能会重复释放或造成内存泄露。
#include "pch.h"
#include <iostream>
#include <cstring>
using namespace std;
class CStudent
{
public:
CStudent(const char* pszName);
CStudent(CStudent& obj);
CStudent& operator=(CStudent& obj);
void release();
void Show()
{
cout << hex << (int&)m_pszName << m_pszName << endl;
}
private:
char* m_pszName;
};
CStudent::CStudent(const char* pszName)
{
m_pszName = new char[256];
strcpy(m_pszName, pszName);
}
CStudent::CStudent(CStudent& obj)
{
//浅拷贝
m_pszName = obj.m_pszName;
//strcpy(m_pszName, obj.m_pszName);
}
CStudent& CStudent::operator=(CStudent& obj)
{
//浅拷贝
m_pszName = obj.m_pszName;
return *this;
}
void CStudent::release()
{
if (m_pszName != NULL)
{
delete m_pszName;
m_pszName = NULL;
}
}
int main(int argc, char* argv[])
{
CStudent stu1("zhang san");
CStudent stu2("li si");
CStudent stu3 = stu2;//在这里设置断点的话,会调用拷贝构造函数
stu1.Show();
stu2.Show();
stu3.Show();
stu2.release();
//stu2.release();会崩掉
stu3.Show();
return 0;
}
鉴于深拷贝和浅拷贝的优缺点,可采用引用计数技术,既减小了内存开销,又避免了堆的重复释放或内存泄露问题。
举例说明
在深拷贝的情况下,通过拷贝构造或者赋值构造的对象均各自包含自己的指针指向“Hello”。
#include "pch.h"
#include <iostream>
#include <cstring>
using namespace std;
class CStudent
{
public:
CStudent(const char* pszName);
CStudent(CStudent& obj);
~CStudent();
CStudent& operator=(CStudent& obj);
void release();
void Show()
{
if (*m_pCount > 0)
{
cout << hex << (int&)m_pszName << m_pszName << endl;
}
}
private:
char* m_pszName;
int * m_pCount;//资源计数器 当资源计数器减为0时,该资源可以被释放,从而解决资源重复释放的问题。
};
CStudent::CStudent(const char* pszName)
{
m_pszName = new char[256];
m_pCount = new int;
strcpy(m_pszName, pszName);
*m_pCount = 1;
}
CStudent::CStudent(CStudent& obj)
{
//浅拷贝
m_pszName = obj.m_pszName;
m_pCount = obj.m_pCount;
(*m_pCount)++;
}
CStudent::~CStudent()
{
release();
}
CStudent& CStudent::operator=(CStudent& obj)
{
if (obj.m_pszName == m_pszName)
{
return *this;
}
if (--(*m_pCount) == 0)
{
delete m_pszName;
m_pszName = NULL;
delete m_pCount;
}
m_pszName = obj.m_pszName;
m_pCount = obj.m_pCount;
(*m_pCount)++;
return *this;
}
void CStudent::release()
{
if (m_pszName != NULL && --*m_pCount == 0)
{
//通过计数器来避免资源的重复释放的问题。
delete m_pszName;
m_pszName = NULL;
delete m_pCount;
}
}
int main(int argc, char* argv[])
{
CStudent stu1("zhang san");
CStudent stu2("li si");
CStudent stu3 = stu2;
stu1.Show();
stu2.Show();
stu3.Show();
stu2.release();
stu3.release();
stu3.Show(