C++智能指针及简单实现
C++智能指针
- 堆内存、栈内存与静态内存
- 静态内存
- 栈内存
- 堆内存
- 动态内存管理
- new、delete运算符
- 智能指针
- 实现智能指针
- shared_ptr
- 智能指针的线程安全问题
- 解决
- unique_ptr
- weak_ptr
- 循环引用
- 思维导图
- 本模块思路
动态内存管理 - cppreference.com
堆内存、栈内存与静态内存
静态内存
- **保存局部static对象。**例如统计函数本身被调用了多少次,可以在函数体内定义一个static对象,不会随函数体结束而销毁,更特别的是,只会在第一次使用时初始化。
- 保存类的static成员。类的static成员仅与类有关,而非与类的每个对象关联,更重要的是,static成员的更新会应用到类的每个对象。
- 全局变量
栈内存
- 函数内的非静态对象
堆内存
- 保存动态分配的对象
动态内存管理
new、delete运算符
- new 在申请内存的同时,还会调用对象的构造函数,返回指向该对象的指针
- delete 在释放内存之前,会调用对象的析构函数
- 易产生问题:内存泄漏(忘记释放)、引用非法内存指针(释放早了)
智能指针
位于memory头文件,智能指针是模板类(类似vector),所以相当于把指针包成一个类,添加一些成员(use_count等)的同时使得指针拥有了构造函数和析构函数,这样我们只需要关注内存的申请,内存的释放则由程序自动完成。
下面先手动实现一个智能指针:
实现智能指针
smartptr.h
#pragma once
#ifndef SMART_PTR_H
#define SMART_PTR_H
#include<iostream>
template<typename T>
class SmartPtr
{
public:
//默认构造函数
SmartPtr() :ptr(nullptr), count(nullptr) {}
//传指针构造函数
SmartPtr(T* _ptr) : ptr(_ptr), count(nullptr)
{
if (_ptr)count = new int(1);
}
//拷贝构造函数
SmartPtr(const SmartPtr& smp)
{
ptr = smp.ptr;
count = smp.count;
if (count)(*count)++;
}
//析构函数
~SmartPtr()
{
reset();
}
//重载=运算符
SmartPtr& operator=(const SmartPtr& smp)
{
if (this == &smp)//指向同一块共享内存
{
return *this;
}
reset();//不一块内存,递减count
this->ptr = smp.ptr;
this->count = smp.count;
if (count)(*count)++;
return *this;
}
//重载*运算符
T operator*() {
return *(this->ptr);
}
//重载->运算符
T* operator->() {
return this->ptr;
}
//取出原始指针
T* get()
{
return this->ptr;
}
//检查是否只有一个共享指针
bool unique()
{
return *count == 1;
}
//返回计数器
int use_count()
{
return *count;
}
//析构时削减共享计数并检查
void reset()
{
if (count)
{
(*count)--;
if (*count == 0)
{
delete this->ptr;
delete this->count;
}
}
}
private:
T* ptr;
int* count;
};
#endif // !SMART_PTR_H
main.cpp
#include<iostream>
#include<memory>
#include"smartptr.h"
using std::make_shared;
using std::shared_ptr;
using std::cout;
using std::endl;
int main()
{
//对比int *sp = 100;
auto sp = make_shared<int>(100);
auto mysp = SmartPtr<int>(new int(100));
cout << "shared_ptr:" << *sp << endl;
cout << "My shared_ptr:" << *mysp << endl;
cout << "shared_ptr use_count:" << sp.use_count() << endl;
cout << "My shared_ptr use_count:" << mysp.use_count() << endl;
cout << "shared_ptr unqiue:" << sp.unique() << endl;
cout << "My shared_ptr unique:" << mysp.unique() << endl;
auto sp2 = shared_ptr<int>(sp);
auto mysp2 = SmartPtr<int>(mysp);
cout << "shared_ptr use_count:" << sp.use_count() << endl;
cout << "My shared_ptr use_count:" << mysp.use_count() << endl;
cout << "shared_ptr unqiue:" << sp.unique() << endl;
cout << "My shared_ptr unique:" << mysp.unique() << endl;
auto p = sp.get();
auto myp = mysp.get();
auto sp3 = make_shared<int>();
auto mysp3 = SmartPtr<int>();
sp3 = sp2;
mysp3 = mysp2;
cout << "shared_ptr use_count:" << sp.use_count() << endl;
cout << "My shared_ptr use_count:" << mysp.use_count() << endl;
cout << "shared_ptr unqiue:" << sp.unique() << endl;
cout << "My shared_ptr unique:" << mysp.unique() << endl;
auto sp4 = make_shared<int>(10);
auto mysp4 = SmartPtr<int>(new int(10));
mysp3 = mysp4;
sp3 = sp4;
cout << "shared_ptr use_count:" << sp.use_count() << endl;
cout << "My shared_ptr use_count:" << mysp.use_count() << endl;
cout << "shared_ptr unqiue:" << sp.unique() << endl;
cout << "My shared_ptr unique:" << mysp.unique() << endl;
getchar();
return 0;
}
shared_ptr
智能指针会将new和delete的过程自动化,它本质上是一个原始指针的包装。
一个允许多个对象指向同一块内存的指针对象,会对该内存地址的引用数量进行计数,shared ptr中除了有一个指针,指向所管理数据的地址。还有一个指针指向一个控制块的地址,里面存放了所管理数据的数量 (常说的引用计数) 、weak ptr的数量、删除器、分配器等,这里控制块是线程安全的,但是所管理数据的地址不是,
智能指针的线程安全问题
多个线程同时修改同一个shared_ptr对象,线程不安全
两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,原因可看i++是原子操作吗,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了,具体可以看C++ 智能指针线程安全的问题中的例子。
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何context switch (切换到另一个线程)。 通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。
解决
加锁,但是更好的方法是尽量不要多线程地改变指针指向
也可以用原子智能指针:C++ 20 引入了原子智能指针std::atomic<std::shared_ptr>
unique_ptr
unique_ptr 不共享它所管理的对象,两个unique_ptr不能指向同一个对象,unique”占有“它的指针,不能赋值和拷贝,智能释放指针或是对控制权进行转移。
使用时,先include
这样我们就定义了一个名为entity的智能指针,一个更好的出于防止构造函数抛出异常的定义是:
weak_ptr
使用weak_ptr复制shared_ptr时不会增加引用计数,这是它最大的特点
这种指针不具有指针功能,最大作用在于解决循环引用的问题
循环引用
可以理解为形成了循环链表,A要释放就要先释放指向A的B,B要释放就要先释放指向B的A,这种时候会造成内存泄漏,此时将其中一个改成weak_ptr,然后在需要获取操作权的时候使用weak_ptr.lock()返回指向共享内存空间的shared_ptr()即可,详见c++ weak ptr解除指针循环引用