四大智能指针与RAII
https://blog.csdn.net/Ferlan/article/details/86513679
RAII即用对象管理资源,在对象的生命周期内,该资源一直有效,当对象被析构时,相应的资源被释放。
而智能指针就是基于这个原理,new关键字开辟的内存往往需要手动delete,一旦程序员忘记delete,则会造成内存泄漏,所以引用智能指针,通过智能指针对象来管理该指针,即管理该内存资源。当智能指针对象析构时,不容的智能指针对象采用不同的释放资源策略。
auto_ptr/unique_ptr/shared_ptr/weak_ptr
- unique_ptr:一种智能指针,其拥有其所指向的对象的独有权
- 不能拷贝和赋值到其他unique_ptr中
- 但是可以进行移动操作,用于接管其他unique_ptr的对象,从而将内存资源的所有权进行转移
- auto_ptr 被淘汰
- shared_ptr 一个标准的共享所有权的智能指针,即该指针所指向的对象可以被其他智能指针所共享
- 允许多个指针指向同一个对象
- 通过引用计数的方式实现了对所管理的对象的分享,只有当管理该对象的所有智能指针全部析构时,才会释放该对象资源
- shared_ptr引入引用计数会加大空间开销
- weak_ptr,是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象,将该指针绑定到一个shared_ptr上不会改变引用计数,一旦shared_ptr所指向的对象被释放,虽然weak_ptr还是指向,但是对象还是被释放了
- 一种对对象的弱引用,不会增加对象的引用计数
- 可以和shared_ptr之间互相转换
- 主要是用于解决shared_ptr的循环引用的问题
shared_ptr所造成的循环引用
当两个对象使用shared_ptr来管理,同时互相使用一个shared_ptr成员变量指向对方,则会造成循环引用
class B; // 前置声明
class A {
public:
shared_ptr<B> ptr;
};
class B {
public:
shared_ptr<A> ptr;
};
int main()
{
while(true) {
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa -> ptr = pb;
pb -> ptr = pa;
}
return 0;
}
解决这种问题的最好方法是才对象内部采用weak_ptr来指向对方(把一个改成weak_ptr即可)
shared_ptr是线程安全的,标准库对其的实现,使用原子操作来操作引用计数
new/delete malloc/free
malloc/free 是标准库函数,其作用是分配空间和释放空间,但是其分配空间不会在空间上构造相应的对象,即是未初始化的,未构造的,不可访问的空间,而free只是释放空间,但是并不会调用空间中存储的对象的析构函数。
new/delete是C++的操作符
new pointer 的实际步骤是
- 调用operator new /operator new[]重载运算符,分配原始内存空间
- 调用对象的默认构造函数,在所分配的内存上构造对象
delete的实际步骤是
- 对内存中保存的对象,显示调用析构函数,析构相应的对象
- 调用operator delete/operator delete[] 重载运算符,释放所分配的空间
这里就要注意一个问题,即delete p和delete [] p的区别,我们常规的理解认为前者用于释放单个对象,后者用于释放一组对象,其实无论是前者还是后者都会释放掉所有申请的堆内存,但是前者只会调用第一个元素的析构函数,而后者会调用数组中所有元素的析构函数,故对于内置类型,delete和delete[]没有区别,但是自定义类型则有区别
标准库允许用户定义重载的operator new/operator new[] operator delete/operator delete[]用于定义自己的内存分配和释放操作
标准库中提供了几种重载的new 和 delete函数
void* operator new(size_t);
void* operator new[](size_t);
void* operator delete(void*) noexcept;
void* operator delete[](void*) noexcept;
......
上述的函数可以被重载但是,operator new(size_t , void*)是不能被重载的,因为该函数有特殊的用处
实际而言operator new/operator free 实际上就是调用的malloc和free函数,其只能分配原始内存空间,或者只能删除析构后的空间,那么谁来负责在内存上构造和析构元素呢?
定位new表达式
new(pointer)type;
new(pointer)type (initializers);
new(pointer)type [size];
new(pointer)type [size] {};
定位表达式主要用于调用构造函数在内存中构造元素,该表达式会先调用operator new(size_t , void*),该函数不干任何事情,只是返回实参,然后由new表达式负责初始化
显示析构,即通过对象的指针显示调用析构函数
p->~A();
关于shared_ptr的线程安全问题
- 首先shared_ptr其内部实现是线程安全的,即其内部引用计数的实现是线程安全的,在标准库中引用计数是原子操作,所以是线程安全的
- 但是对于shared_ptr所指向的对象而言,其并不是线程安全的,即可以同时读取一个shared_ptr,但是不能同时读写/同时写一个shared_ptr,这样并不是线程安全的