C/C++开发,无可避免的内存管理(篇四)-智能指针备选
一、智能指针
采用C/C++开发堆内存管理无论是底层开发还是上层应用,无论是开发新手,还是多年的老手,都会不自觉中招,尤其是那些不是自己一手经历的代码,要追溯问题出在哪里更是个麻烦事。C/C++程序常常会遇到程序突然退出,占用内存越来越多、定期重启等症状,可能都是堆内存管理没有正确处理好内存分配和释放,出现野指针、重复释放、内存泄漏等语法错误:
- 野指针,一些内存单元已经被释放,之前指向它的指针确还在被使用,导致无法预测的错误。
- 重复释放,程序试图去释放已经被释放过的内存单元,或者释放已经被重新分配过的内存单元,导致重复释放错误,系统会引出触发大量的错误及诊断信息输出。
- 内存泄漏,不再需要使用的内存单元没有被释放,程序不断地重复运行这些指令,会导致内存没被回收而占用大量内存,造成程序内存使用不断在增加。
而智能指针就是用来解决程序资源生存期管理的问题(尤其是动态分配的对象,堆内存管理)。 智能指针有各种不同的风格。多数都有一种共同的关键特性:自动资源管理。这种特性可能以不同的方式出现:如动态分配对象的生存期控制,和获取及释放资源 (文件,网络连接)。大多智能指针主要针对第一种情况,它们保存指向动态分配对象的指针,并在正确的时候删除这些对象。除了这些功能以外,智能指针要尽可能少做其他工作。虽然可以通过程序设计,使得它们增加功能,覆盖所有资源管理的不同情况,但是需要付出代价的,通常这样的解决方案意味着更高的复杂性。
如果能显式管理内存,其实在性能上是有一定的优势的,但是也意味着内存异常风险增加,尤其在多线程编程领域,内存管理不佳的情况可能会更加严重。为了让开发人员专注于业务开发,摆脱内存管理的细节,c++标准库引入了智能指针。
二、auto_ptr
在C++98,智能指针通过一个类模板“auto_ptr ” 来实现的,它以对象的方式管理堆分配的内存,并在适当的时机,释放所获得的堆内存。开发者使用auto_ptr模板时,将new操作返回的指针作为其初始值即可,在后面就不再需要开发者调用delete手动释放堆内存了。
#include <memory>std::auto_ptr<int> pi(new int);
*pi = 100;
这在一定程度上避免了堆内存忘记释放造成的问题。
2.1 仿auto_ptr 的类模板
下面来看看auto_ptr 模板的具体实现原理。创建一个类似auto_ptr 模板的AutoPtr类模板,提供显式声明,构造时需要传入类型指针,整个AutoPtr类模板的主要作用就是管理这个指针(指向模板参数类型的指针)的生存周期。
#ifndef _AUTO_PTR_H_
#define _AUTO_PTR_H_#include <iostream>template<typename T>
class AutoPtr
{
public:explicit AutoPtr(T* p=0) : m_ptr(p) {std::cout << "AutoPtr create!\n";};~AutoPtr() { std::cout << "AutoPtr delete!\n";delete m_ptr; };T& operator*() const { return *m_ptr; };T* operator->() const { return m_ptr;};private:T* m_ptr; // dumb pointer
};void func1(void)
{AutoPtr<int> a(new int(100));std::cout << "*a = " << *a << "\n";
}#include "autoptr.cpp"
#endif // _AUTO_PTR_H_
上述代码就将一个指针放置在一个对象中管理,而C/C++语言中,对于对象会对其自动释放内存操作。所以AutoPtr类模板的本质就是将指向T类型的指针伪装成一个对象,然后让C/C++语法管理对象一样间接管理了该指针。智能指针可以像普通指针那样使用,因为其提供了operator*和operator->操作符,满足向普通指针一样的"."和"->"功能,使得使用AutoPtr<T>对象和使用T*指针一样,没有改变原来使用T*指针的语法习惯和结构。
#include "autoptr.h"
/*
void func1(void)
{AutoPtr<int> a(new int(100));std::cout << "*a = " << *a << "\n";
}
*/
int main(int argc, char* argv[])
{func1();return 0;
}
//out log
AutoPtr create!
*a = 100
AutoPtr delete!
上述代码中,在一个函数内创建一个AutoPtr<int>对象,因为是对象,函数结束后(离开作用域)会自动释放,因此调用了AutoPtr的析构函数,因而间接释放int*指针指向的内存。
2.2 auto_ptr 模板应用缺陷
当然标准库中的auto_ptr 模板具有更多的功能:
get() //获取智能指针托管的指针地址
release() //取消智能指针对动态内存的托管
reset(T* ptr_=nullptr) //重置智能指针托管的内存地址,默认为null
不过auto_ptr 因为拷贝时返回一个左值,复制或者赋值都会改变资源的所有权,在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值,不支持对象数组的内存管理和不能调用delete[]等缺陷问题,在C++11标准中,被废弃了。对于auto_ptr 开发者保持了解即可,这是为了阅读一些旧代码需要,但是自行编写代码时,还是不要再使用auto_ptr ,而采用c++11后的智能指针吧。
三、C++11 的智能指针
3.1 C++11 智能指针应用
C++11 增加unique_ptr、shared_ptr 和weak_ptr,定义于头文件 <memory>。
- unique_ptr,C++11用更严谨的unique_ptr 取代了auto_pt,和 auto_ptr用法几乎一样,除了拥有独有对象所有权语义的智能指针和一些特殊用法。
- shared_ptr,进行对象的生存期自动管理,拥有共享对象所有权语义的智能指针,使得分享资源所有权变得有效且安全。
- weak_ptr,可以安全地观测共享资源,对被 std::shared_ptr 管理的对象存在非拥有性(弱)引用,避免了悬挂的指针。
在探究这几个智能指针的原理前,先看看它们的用法和auto_ptr 又有那些区别:
//ptrc11_test.h
#include <memory>
//#include <vector>
using namespace std;void out_shared(weak_ptr<int> &wp)
{shared_ptr<int> sp = wp.lock();if(nullptr!=sp){cout << "*wp1 = " << *sp << "\n";//20}else{cout << "pointer is invalid.\n";//}
}void func2(void)
{unique_ptr<int> up1(new int(10));////unique_ptr<int> up2=up1;//error,不能通过编译,无法赋值//unique_ptr<int> up3(up1);//error,不能通过编译cout << "*up1 = " << *up1 << "\n";//10unique_ptr<int> up3 = move(up1);//up3获得数据10唯一的unique_ptr智能指针cout << "*up3 = " << *up3 << "\n";//OK,10//cout << "*up1 = " << *up1 << "\n";//error,能编译但运行时出错up3.reset(); //显式释放内存up1.reset(); //OK,可编译,不会导致运行时错误//cout << "*up3 = " << *up3 << "\n";//error,能编译但运行时出错shared_ptr<int> sp1(new int(20));shared_ptr<int> sp2=sp1; //OKweak_ptr<int> wp1 = sp1;//指向shared_ptr<int>所指对象cout << "*sp1 = " << *sp1 << "\n";//20cout << "*sp2 = " << *sp2 << "\n";//20out_shared(wp1);//sp1.reset();//cout << "*sp1 = " << *sp1 << "\n";//error,能编译但运行时出错cout << "*sp2 = " << *sp2 << "\n";//OK,20out_shared(wp1);//sp2.reset();out_shared(wp1);
}#include "ptrc11_test.h"
int main(int argc, char* argv[])
{func2();return 0;
}
//out log
*up1 = 10
*up3 = 10
*sp1 = 20
*sp2 = 20
*wp1 = 20
*sp2 = 20
*wp1 = 20
pointer is invalid.
上述代码,让智能指针指向一个int*指针指向的对象,由于每个智能指针都重载了*运算符,因此可以使用个*智能指针的方式访问所分配的堆内存。同时智能指针除了退出作用域自动释放堆内存外,还提供了reset成员函数主动释放其拥有的堆内存。从堆内存管理功能上来说,unique_ptr和shared_ptr和原来的auto_ptr是保持一致的。
所区别的是,unique_ptr对所指向独享的内存是独占模式,不能与其他unique_ptr类型指针共享所指向的对象的内存。每个unique_ptr都是唯一占有所指向的对象内存,因此不能直接赋值给其他unique_ptr。但是这种独占所有权可以通过std::move函数来转移,转移后,旧unique_ptr指针就失去该对象内存的所有权,不能再使用,否则会运行错误。
如果我们了解过单体类(单体模式)就比较好理解unique_ptr的本质是什么。unique_ptr就是一个删除了拷贝构造函数、保留了移动构造函数的指针封装类型。开发者仅可以使用右值对unique_ptr对象进行构造,一旦构造成功,它就获得了右值对象的指针,而右值对象则失去了对指针的“所有权”。
shared_ptr正如其名一样,是允许多个shared_ptr指针共享同一个堆分配对象的内存的。它实际上就我们设计类时引入了计数方式。即一个shared_ptr指向了该对象的内存,计数+1,相应地,如果一个shared_ptr放弃了指向该对象内存,则计数-1。所以shared_ptr对象的reset成员就是将计数-1而已,只有计数归零时,shared_ptr才会真正释放所占有的堆内存空间。
智能指针weak_ptr会复杂一些,由于shared_ptr是shared_ptr的弱引用,因此一般是与shared_ptr配套起来使用。它可以指向shared_ptr指针指向的对象内存,但却不拥有该对象内存。在实际使用weak_ptr时,是通过函数成员lock返回一个weak_ptr对象(所指对象内存无效时,返回nullptr)。这可以用来辅助验证shared_ptr智能指针的有效性。上述代码中两个shared_ptr都主动释放对象内存的所有权后,weak_ptr的lock返回空指针。
3.2 unique_ptr智能指针
unique_ptr定义于头文件<memory>,std::unique_ptr 有两个版本,声明如下:
- 管理单个对象(例如以 new 分配)。
- 管理动态分配的对象数组(例如以 new[] 分配。
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;template <class T,class Deleter>
class unique_ptr<T[], Deleter>;//对象定义
unique_ptr<int> array(new int);
unique_ptr<int[]> array(new int[5]);
std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。unique_ptr释放占用对象有两种方式:
- 离开作用域,调用析构销毁了管理的 unique_ptr 对象,通过调用 get_deleter()(ptr) ,用潜在为用户提供的删除器释放对象。默认删除器用 delete 运算符,它销毁对象并解分配内存。
- 通过 operator= 或 reset() 赋值另一指针给管理的 unique_ptr 对象。只有非 const 的 unique_ptr 能转移被管理对象的所有权给另一 unique_ptr 。若对象的生存期为 const std::unique_ptr 所管理,则它被限定在创建指针的作用域中。
unique_ptr 亦可以不占有对象,该情况下称它为空 (empty)。
unique_ptr<int> pu1;
unique_ptr提供移动构造 (MoveConstructible) 和移动赋值 (MoveAssignable) 的操作,但不能复制构造 (CopyConstructible) 或复制赋值 (CopyAssignable) 的操作,std::unique_ptr 常用于管理对象的生存期,具有以下应用:
- 通过正常退出和经由异常退出两者上的受保证删除,提供异常安全,给处理拥有动态生存期的对象的类和函数
- 两个指针不能指向同一个资源无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,允许临时右值赋值构造和赋值,通过move函数实现
- 传递独占的拥有动态生存期的对象的所有权到函数
- 从函数获得独占的拥有动态生存期对象的所有权
- 在容器中保存指针是安全的,作为具移动容器的元素类型,例如保有指向动态分配对象的指针的 std::vector (例如,若想要多态行为)。
unique_ptr作为容器元素是安全的,但不允许直接赋值。
//ptrc11_test.h
void func3(void)
{vector<unique_ptr<string>> u_vec;unique_ptr<string> pu1(new string("I'm Pu1"));unique_ptr<string> pu2(new string("I'm Pu2"));u_vec.push_back(std::move(pu1));u_vec.push_back(std::move(pu2));cout << "u_vec.at(0):" << *u_vec.at(0) << endl;cout << "u_vec[1]:" << *u_vec[1] << endl;//u_vec[0] = u_vec[1]; /* 不允许直接赋值 */u_vec[0] = std::move(u_vec[1]); // 需要使用move修饰,标记对象内存所有权已被转移cout << "u_vec.at(0):" << *u_vec.at(0) << endl;//cout << "u_vec[1]:" << *u_vec[1] << endl; //运行错误,对象内存所有权已被转移string *str_ = new string("I'm str");u_vec[1].reset(str_); //cout << "u_vec[1]:" << *u_vec[1] << endl; //OK,获得新的对象内存所有权cout << "*str_:" << *str_ << endl;//Oku_vec[0] = std::move(u_vec[1]); // 需要使用move修饰,标记对象内存所有权已被转移cout << "u_vec.at(0):" << *u_vec.at(0) << endl;//cout << "u_vec[1]:" << *u_vec[1] << endl; //运行错误,对象内存所有权已被转移cout << "*str_:" << *str_ << endl;//Ok
}//out log
u_vec.at(0):I'm Pu1
u_vec[1]:I'm Pu2
u_vec.at(0):I'm Pu2
u_vec[1]:I'm str
*str_:I'm str
u_vec.at(0):I'm str
*str_:I'm str
完整认识unique_ptr类模板:
成员类型 定义
pointer 若该类型存在则为 std::remove_reference<Deleter>::type::pointer ,否则为 T* 。必须满足可空指针 (NullablePointer) 。
element_type T ,此 unique_ptr 所管理的对象类型
deleter_type Deleter ,函数对象或到函数或到函数对象的左值引用,会从析构函数调用 成员函数
(构造函数) 构造新的unique_ptr,public
(析构函数) 析构所管理的对象,如果存在的话,public
operator= 为unique_ptr赋值,public修改器
release 返回一个指向被管理对象的指针,并释放所有权,public
reset 替换被管理对象,public
swap 交换被管理对象,public观察器
get 返回指向被管理对象的指针,public
get_deleter 返回用于析构被管理对象的删除器,publicoperator bool 检查是否有关联的被管理对象,public单对象版本,unique_ptr<T>
operator* 解引用指向被管理对象的指针,public
operator-> 解引用指向被管理对象的指针,public数组版本, unique_ptr<T[]>
operator[] 提供到被管理数组的有索引访问,public非成员函数
make_unique 创建管理一个新对象的独占指针(C++14)
make_unique_for_overwrite 创建管理一个新对象的独占指针(C++20,函数模板)operator==
operator!= (C++20 中移除)
operator< 与另一个 unique_ptr 或 nullptr 进行比较,(函数模板)
operator<=
operator>
operator>=
operator<=> (C++20)
operator<< (C++20) 输出被管理指针的值到输出流,(函数模板) std::swap(std::unique_ptr) (C++11),特化std::swap算法(函数模板) 辅助类
std::hash<std::unique_ptr> (C++11)std::unique_ptr 的散列支持(类模板特化)
unique_ptr智能指针应用:
#ifndef _UNIQUE_PTR_TEST_H_
#define _UNIQUE_PTR_TEST_H_#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional>struct B {virtual void bar() { std::cout << "B::bar\n"; }virtual ~B() = default;
};
struct D : B
{D() { std::cout << "D::D\n"; }~D() { std::cout << "D::~D\n"; }void bar() override { std::cout << "D::bar\n"; }
};// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{p->bar();return p;
}void close_file(std::FILE* fp) { std::fclose(fp); }void func4(void)
{std::cout << "unique ownership semantics demo\n";{auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptrauto q = pass_through(std::move(p)); assert(!p); // 现在 p 不占有任何内容并保有空指针q->bar(); // 而 q 占有 D 对象} // ~D 调用于此std::cout << "Runtime polymorphism demo\n";{std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr// 作为指向基类的指针p->bar(); // 虚派发std::vector<std::unique_ptr<B>> v; // unique_ptr 能存储于容器v.push_back(std::make_unique<D>());v.push_back(std::move(p));v.emplace_back(new D);for(auto& p: v) p->bar(); // 虚派发} // ~D called 3 timesstd::cout << "Custom deleter demo\n";std::ofstream("demo.txt") << 'x'; // 准备要读的文件{std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"),close_file);if(fp) // fopen 可以打开失败;该情况下 fp 保有空指针std::cout << (char)std::fgetc(fp.get()) << '\n';} // fclose() 调用于此,但仅若 FILE* 不是空指针// (即 fopen 成功)std::cout << "Custom lambda-expression deleter demo\n";{std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr){std::cout << "destroying from a custom deleter...\n";delete ptr;}); // p 占有 Dp->bar();} // 调用上述 lambda 并销毁 Dstd::cout << "Array form of unique_ptr demo\n";{std::unique_ptr<D[]> p{new D[3]};} // 调用 ~D 3 次
}
#endif
//out log
unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
Custom deleter demo
x
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D
3.3 shared_ptr智能指针
std::shared_ptr智能指针同样定义于头文件<memory>,声明如下:
// (C++11 起)
template< class T > class shared_ptr;
shared_ptr 能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。存储的指针为 get() 、解引用及比较运算符所访问。
std::shared_ptr智能指针采用了对象引用计数策略,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放对象内存。
std::shared_ptr 是多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存,被管理指针是在 use_count 抵达零时传递给删除器者。:
- 最后剩下的占有对象的 shared_ptr 被销毁,用 delete 表达式或在构造期间提供给 shared_ptr 的定制删除器销毁对象;
- 最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。
shared_ptr 亦可不占有对象,该情况下称它为空 (empty) (空 shared_ptr 可拥有非空存储指针,若以别名使用构造函数创建它)。
shared_ptr<int> sp1;
引用计数通过use_count成员函数获取:
void func5(void)
{int *pi = new int(100);shared_ptr<int> sp1;shared_ptr<int> sp2(pi);cout << "sp1.use_count() = " << sp1.use_count() << "\n";cout << "sp2.use_count() = " << sp2.use_count() << "\n";sp1 = sp2;cout << "sp1.use_count() = " << sp1.use_count() << "\n";cout << "sp2.use_count() = " << sp2.use_count() << "\n";shared_ptr<int> sp3(sp1);cout << "sp1.use_count() = " << sp1.use_count() << "\n";cout << "sp2.use_count() = " << sp2.use_count() << "\n";cout << "sp3.use_count() = " << sp3.use_count() << "\n";
}
//out log
sp1.use_count() = 0
sp2.use_count() = 1
sp1.use_count() = 2
sp2.use_count() = 2
sp1.use_count() = 3
sp2.use_count() = 3
sp3.use_count() = 3
在典型的实现中, std::shared_ptr 只保有二个指针:
- get() 所返回的指针
- 指向控制块的指针
控制块是一个动态分配的对象,其中包含:
- 指向被管理对象的指针或被管理对象本身
- 删除器(类型擦除)
- 分配器(类型擦除)
- 占有被管理对象的 shared_ptr 的数量
- 涉及被管理对象的 weak_ptr 的数量
shared_ptr 的所有特化满足可复制构造 (CopyConstructible) 、可复制赋值 (CopyAssignable) 和可小于比较 (LessThanComparable) 的要求并可按语境转换为 bool 。
std::shared_ptr 可以用于不完整类型 T 。但参数为裸指针的构造函数( template<class Y> shared_ptr(Y*) )和 template<class Y> void reset(Y*) 成员函数只可以用指向完整类型的指针调用(注意 std::unique_ptr 可以从指向不完整类型的裸指针构造)。
shared_ptr 不确保线程绝对安全,需要使用者自行负责多线程下其使用语境与安全设计。多个线程能在 shared_ptr 的不同实例上调用所有成员函数(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是副本,且共享同一对象的所有权。若多个执行线程访问同一 shared_ptr 而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;原子函数的 shared_ptr 特化能用于避免数据竞争。
shared_ptr类模板包含以下功能支持:
成员类型 定义
element_type T (C++17 前) ,std::remove_extent_t<T> (C++17 起) weak_type (C++17 起) std::weak_ptr<T> 成员函数
(构造函数) 构造新的 shared_ptr,public
(析构函数) 如果没有更多 shared_ptr 指向持有的对象,则析构对象,public
operator= 对 shared_ptr 赋值,public修改器
reset 替换所管理的对象,public
swap 交换所管理的对象,public观察器
get 返回存储的指针,publicoperator* 解引用存储的指针,public
operator-> 解引用存储的指针,public
operator[] (C++17),提供到被存储数组的带下标访问,public
use_count 返回 shared_ptr 所指对象的引用计数,public
unique (C++20 前),检查所管理对象是否仅由当前 shared_ptr 的实例管理,public
operator bool 检查是否有关联的管理对象,public
owner_before 提供基于拥有者的共享指针排序,public非成员函数
make_shared 创建管理一个新对象的共享指针(函数模板)
make_shared_for_overwrite 创建管理一个新对象的共享指针(函数模板) , (C++20)allocate_shared 创建管理一个用分配器分配的新对象的共享指针(函数模板)
allocate_shared_for_overwrite 创建管理一个用分配器分配的新对象的共享指针(函数模板) ,(C++20)static_pointer_cast 应用 static_cast到被存储指针(函数模板)
dynamic_pointer_cast 应用 dynamic_cast到被存储指针(函数模板)
const_pointer_cast 应用 const_cast到被存储指针(函数模板)
reinterpret_pointer_cast 应用 reinterpret_cast到被存储指针(函数模板),(C++17)get_deleter 返回指定类型中的删除器,若其拥有(函数模板) operator== (函数模板)
operator!= (C++20 中移除) 与另一个 shared_ptr 或 nullptr 进行比较
operator< (C++20 中移除)
operator<= (C++20 中移除)
operator> (C++20 中移除)
operator>= (C++20 中移除)
operator<=> (C++20)(函数模板)
operator<< 将存储的指针的值输出到输出流(函数模板) std::swap(std::shared_ptr) (C++11),特化 std::swap 算法(函数模板)
/*下列,(函数模板) ,特化的原子操作,(C++20 中弃用)*/
std::atomic_is_lock_free(std::shared_ptr)
std::atomic_load(std::shared_ptr)
std::atomic_load_explicit(std::shared_ptr)
std::atomic_store(std::shared_ptr)
std::atomic_store_explicit(std::shared_ptr)
std::atomic_exchange(std::shared_ptr)
std::atomic_exchange_explicit(std::shared_ptr)
std::atomic_compare_exchange_weak(std::shared_ptr)
std::atomic_compare_exchange_strong(std::shared_ptr)
std::atomic_compare_exchange_weak_explicit(std::shared_ptr)
std::atomic_compare_exchange_strong_explicit(std::shared_ptr)辅助类
std::hash<std::shared_ptr> (C++11),std::shared_ptr 的散列支持(类模板特化)
std::atomic<std::shared_ptr> (C++20),原子共享指针(类模板特化)
shared_ptr 智能指针应用:
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>struct Base
{Base() { std::cout << " Base::Base()\n"; }// 注意:此处非虚析构函数 OK~Base() { std::cout << " Base::~Base()\n"; }
};struct Derived: public Base
{Derived() { std::cout << " Derived::Derived()\n"; }~Derived() { std::cout << " Derived::~Derived()\n"; }
};void thr(std::shared_ptr<Base> p)
{std::this_thread::sleep_for(std::chrono::seconds(1));std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count{static std::mutex io_mutex;std::lock_guard<std::mutex> lk(io_mutex);std::cout << "local pointer in a thread:\n"<< " lp.get() = " << lp.get()<< ", lp.use_count() = " << lp.use_count() << '\n';}
}void func6(void)
{std::shared_ptr<Base> p = std::make_shared<Derived>();std::cout << "Created a shared Derived (as a pointer to Base)\n"<< " p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';std::thread t1(thr, p), t2(thr, p), t3(thr, p);p.reset(); // 从 main 释放所有权std::cout << "Shared ownership between 3 threads and released\n"<< "ownership from main:\n"<< " p.get() = " << p.get()<< ", p.use_count() = " << p.use_count() << '\n';t1.join(); t2.join(); t3.join();std::cout << "All threads completed, the last one deleted Derived\n";
}
//g++ main.cpp -o test.exe -std=c++11 -pthread
//out logBase::Base()Derived::Derived()
Created a shared Derived (as a pointer to Base)p.get() = 0x1fd6068, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:p.get() = 0, p.use_count() = 0
local pointer in a thread:lp.get() = 0x1fd6068, lp.use_count() = 4
local pointer in a thread:lp.get() = 0x1fd6068, lp.use_count() = 3
local pointer in a thread:lp.get() = 0x1fd6068, lp.use_count() = 2Derived::~Derived()Base::~Base()
All threads completed, the last one deleted Derived
以调用 std::make_shared 或 std::allocate_shared 创建 shared_ptr 时,以单次分配创建控制块和被管理对象。被管理对象在控制块的数据成员中原位构造。通过 shared_ptr 构造函数之一创建 shared_ptr 时,被管理对象和控制块必须分离分配。此情况中,控制块存储指向被管理对象的指针。
shared_ptr 持有的指针是通过 get() 返回的;而控制块所持有的指针/对象则是最终引用计数归零时会被删除的那个。两者并不一定相等。
shared_ptr 的析构函数会将控制块中的 shared_ptr 计数器减一,如果减至零,控制块就会调用被管理对象的析构函数。但控制块本身直到 std::weak_ptr 计数器同样归零时才会释放。若有共享指针指向同一控制块,则自增弱指针计数。
为满足线程安全要求,引用计数器典型地用等价于用 std::memory_order_relaxed 的 std::atomic::fetch_add 自增(自减要求更强的顺序,以安全销毁控制块)。
3.4 weak_ptr智能指针
std::weak_ptr智能指针,定义于头文件 <memory>,声明如下:
//c++11起
template< class T > class weak_ptr;
std::weak_ptr 对被 std::shared_ptr 管理的对象存在非拥有性(弱)引用。在访问所引用的对象前必须先转换为 std::shared_ptr。std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr 被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。std::weak_ptr 的另一用法是打断 std::shared_ptr 所管理的对象组成的环状引用。若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。能令环中的指针之一为弱指针以避免此情况。
std::weak_ptr 指针功能设计如下:
成员类型 定义
element_type T (C++17 前) ;std::remove_extent_t<T>, (C++17 起) 成员函数
(构造函数) 构造新的weak_ptr,public
(析构函数) 销毁 weak_ptr,public
operator= 为weak_ptr赋值,public修改器
reset 释放被管理对象的所有权,public
swap 交换被管理对象,public观察器
use_count 返回管理该对象的 shared_ptr 对象数量,public
expired 检查被引用的对象是否已删除,public
lock 创建管理被引用的对象的shared_ptr,public
owner_before 提供弱指针的基于拥有者顺序,public非成员函数
std::swap(std::weak_ptr) (C++11), 特化 std::swap 算法(函数模板) 辅助类
std::atomic<std::weak_ptr> (C++20),原子弱指针(类模板特化)
weak_ptr一般和std::shared_ptr一同使用, weak_ptr被用于指向控制块的指针和作为构造来源的shared_ptr的存储指针。需要用分离的存储指针确保 shared_ptr 和 weak_ptr 间的来回转化正确进行,即使对于别名使用的 shared_ptr 。不可能不经将 weak_ptr 中的存储指针锁入 shared_ptr 就访问它。
#ifndef _WEAK_PTR_H_
#define _WEAK_PTR_H_
//g++ main.cpp -o test.exe -std=c++11
#include <iostream>
#include <memory>using namespace std;void out_shared(weak_ptr<int> &wp)
{shared_ptr<int> sp = wp.lock();if(nullptr!=sp){cout << "*wp1 = " << *sp << "\n";//20}else{cout << "pointer is invalid.\n";//}
}void func7(void)
{std::weak_ptr<int> gw;{auto sp = std::make_shared<int>(42);gw = sp;out_shared(gw);}out_shared(gw);
};#endif //_WEAK_PTR_H_
//out log
*wp1 = 42
pointer is invalid.
四、 智能指针辅助类
c++11的头文件 <memory>除了定义上述三种智能指针外,还为这些智能指针提供了辅助类型:
owner_less (C++11),提供基于所有者的,共享指针和弱指针的混合类型的排序(类模板) enable_shared_from_this (C++11),允许对象创建指代自身的 shared_ptr(类模板) bad_weak_ptr (C++11),访问指向已销毁对象的 weak_ptr 时抛出的异常(类) default_delete (C++11),unique_ptr 的默认删除器,(类模板)
4.1 owner_less类
owner_less在memory文件内声明如下:
template< class T > struct owner_less<std::shared_ptr<T> >;//(C++11 起)
template< class T > struct owner_less<std::weak_ptr<T> >;//(C++11 起)
template<> struct owner_less<void>;// (C++17 起,类模板特化,为共享指针和弱指针提供混合类型的、基于拥有者的顺序的函数对象,无关乎被指向的类型)
owner_less是一个结构体模板,此函数对象提供基于拥有者(不同于基于值)的, std::weak_ptr 和 std::shared_ptr 两者的混合类型序。顺序满足二个智能指针比较相等,若且唯若它们均为空或共享所有权,即使由 get() 获得的裸指针值相异(例如因为它们指向同一对象中的不同子对象)。
//在以 std::shared_ptr 或 std::weak_ptr 为关键建立关联容器,即
std::map<std::shared_ptr<T>, U, std::owner_less<std::shared_ptr<T> > >
//或
std::map<std::weak_ptr<T>, U, std::owner_less<std::weak_ptr<T> > > //最好使用此类模板。
//默认的 operator< 不为弱指针定义,并且可能错误地认为同一对象的二个共享指针不等价。
owner_less类功能简述:
成员函数
operator() 用基于拥有者的语义比较其参数(函数) 所有模板特化的成员:
bool operator()( const std::shared_ptr<T>& lhs,const std::weak_ptr<T>& rhs ) const noexcept;//(C++11 起)
bool operator()( const std::weak_ptr<T>& lhs,const std::shared_ptr<T>& rhs ) const noexcept;//(C++11 起) //不同特化类型比较时,用基于拥有者的语义比较 lhs 与 rhs 。等效于调用 lhs.owner_before(rhs) 。顺序是严格弱序关系。
//lhs 与 rhs 相等,当且仅当它们均为空或共享所有权。
//参数,lhs, rhs - 要比较的共享所有权指针
//返回值,若按基于拥有者的顺序确定 lhs 小于 rhs ,则为 true 。仅为 owner_less<shared_ptr<T>> 模板特化的成员:
bool operator()( const std::shared_ptr<T>& lhs,const std::shared_ptr<T>& rhs ) const noexcept;//(C++11 起) 仅为 owner_less<weak_ptr<T>> 模板特化的成员:
bool operator()( const std::weak_ptr<T>& lhs, const std::weak_ptr<T>& rhs ) const noexcept;// (C++11 起)
4.2 enable_shared_from_this类
enable_shared_from_this类声明如下:
//c++11起
template< class T > class enable_shared_from_this;
std::enable_shared_from_this 能让其一个对象(假设其名为 t ,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 std::shared_ptr 实例(假设名为 pt1, pt2, ... ) ,它们与 pt 共享对象 t 的所有权。若一个类 T 继承 std::enable_shared_from_this<T> ,则会为该类 T 提供成员函数: shared_from_this 。 当 T 类型对象 t 被一个为名为 pt 的 std::shared_ptr<T> 类对象管理时,调用 T::shared_from_this 成员函数,将会返回一个新的 std::shared_ptr<T> 对象,它与 pt 共享 t 的所有权。
enable_shared_from_this类功能简述:
成员函数
(构造函数) 构造 enable_shared_from_this 对象,protected
(析构函数) 销毁 enable_shared_from_this 对象,protected
operator= 返回到 this 的引用,protected
shared_from_this 返回共享 *this 所有权的 shared_ptr,public
weak_from_this (C++17),返回共享 *this 所有权的 weak_ptr,public成员对象
weak_this (C++17)追踪 *this 的首个共享占有者的控制块的 std::weak_ptr 对象,private
enable_shared_from_this内部保存着一个对 this 的弱引用(例如 std::weak_ptr )。 std::shared_ptr 的构造函数检测无歧义且可访问的 (C++17 起) enable_shared_from_this 基类,并且若内部存储的弱引用未为生存的 std::shared_ptr 占有,则 (C++17 起)赋值新建的 std::shared_ptr 为内部存储的弱引用。为已为另一 std::shared_ptr 所管理的对象构造一个 std::shared_ptr ,将不会考虑内部存储的弱引用,从而将导致未定义行为。
enable_shared_from_this 提供安全的替用方案,以替代 std::shared_ptr<T>(this) 这种不安全的的表达式:
#ifndef _ENABLE_SHARED_TEST_H_
#define _ENABLE_SHARED_TEST_H_#include <memory>
#include <iostream>struct Good: std::enable_shared_from_this<Good> // 注意:继承
{std::shared_ptr<Good> getptr() {return shared_from_this();}
};struct Bad
{// 错误写法:用不安全的表达式试图获得 this 的 shared_ptr 对象std::shared_ptr<Bad> getptr() {return std::shared_ptr<Bad>(this);}~Bad() { std::cout << "Bad::~Bad() called\n"; }
};void func9(void)
{// 正确的示例:两个 shared_ptr 对象将会共享同一对象std::shared_ptr<Good> gp1 = std::make_shared<Good>();std::shared_ptr<Good> gp2 = gp1->getptr();std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';// 错误的使用示例:调用 shared_from_this 但其没有被 std::shared_ptr 占有try {Good not_so_good;std::shared_ptr<Good> gp1 = not_so_good.getptr();} catch(std::bad_weak_ptr& e) {// C++17 前为未定义行为; C++17 起抛出 std::bad_weak_ptr 异常std::cout << e.what() << '\n'; }// 错误的示例,每个 shared_ptr 都认为自己是对象仅有的所有者std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();std::shared_ptr<Bad> bp2 = bp1->getptr();std::cout << "bp2.use_count() = " << bp2.use_count() << '\n';
} // UB : Bad 对象将会被删除两次#endif
//out log
gp2.use_count() = 2
bad_weak_ptr
bp2.use_count() = 1
Bad::~Bad() called
Bad::~Bad() called
4.3 bad_weak_ptr类
bad_weak_ptr类是 std::shared_ptr 以 std::weak_ptr 为参数的构造函数,在 std::weak_ptr 指代已被删除的对象时,作为异常抛出的对象类型,定义在头文件 <memory>:
//c++11起,继承自 std::exception
class bad_weak_ptr;
bad_weak_ptr类功能:
成员函数
(构造函数) 构造新的 bad_weak_ptr 对象,public
bad_weak_ptr() noexcept;
bad_weak_ptr( const bad_weak_ptr& other ) noexcept; operator= 替换 bad_weak_ptr 对象,public
bad_weak_ptr& operator=( const bad_weak_ptr& other ) noexcept;what 返回解释字符串,public
virtual const char* what() const noexcept;
在 std::weak_ptr 指代已被删除的对象时,作为异常抛出的对象类型示例:
#ifndef _BAD_WEAK_PTR_H_
#define _BAD_WEAK_PTR_H_#include <memory>
#include <iostream>void func10(void)
{std::shared_ptr<int> p1(new int(42));std::weak_ptr<int> wp(p1);p1.reset();try {std::shared_ptr<int> p2(wp);} catch(const std::bad_weak_ptr& e) {std::cout << e.what() << '\n';}
}#endif //_BAD_WEAK_PTR_H_
//out log
bad_weak_ptr
4.4 default_delete类
std::default_delete 是不指定删除器时 std::unique_ptr 所用的默认删除策略,声明如下:
//C++11 起
template< class T > struct default_delete;
template< class T > struct default_delete<T[]>;
default_delete 的特化在典型实现上为空类,并且用于空基类优化。
- 非特化的 default_delete 用 delete 解分配单个对象的内存。
- 亦为提供数组类型的使用 delete[] 的部分特化。
default_delete类功能函数:
(构造函数) 构造 default_delete 对象,public
constexpr default_delete() noexcept = default;
template <class U> default_delete( const default_delete<U>& d ) noexcept;//仅为初等 default_delete 模板的成员)
template<class U> default_delete( const default_delete<U[]>& d ) noexcept;//仅为 default_delete<T[]> 模板特化的成员)operator() 删除对象或数组,public
void operator()(T* ptr) const; //仅为初等 default_delete 模板的成员
template <class U> void operator()(U* ptr) const;//仅为 default_delete<T[]> 模板特化的成员,在 ptr 上调用 delete[] /*在代码中调用 operator(),类型必须完整。一些实现中用 static_assert 确保如此。此要求的原因,是 C++ 中若完整类类型拥有非平凡析构函数或解分配函数,则在不完整类型上调用 delete 是未定义行为,因为编译器无法得知这种函数是否存在且必须被调用。*/
不指定删除器时 std::unique_ptr 调用std::default_delete 删除策略示例:
#ifndef _DEFAULT_DELETE_TEST_H_
#define _DEFAULT_DELETE_TEST_H_#include <memory>
#include <vector>
#include <algorithm>void func11(void)
{{
// std::shared_ptr<int> shared_bad(new int[10]);} // 析构函数调用 delete ,未定义行为{std::shared_ptr<int> shared_good(new int[10], std::default_delete<int[]>());} // 析构函数调用 delete[] , ok{std::unique_ptr<int> ptr(new int(5));} // unique_ptr<int> 使用 default_delete<int>{std::unique_ptr<int[]> ptr(new int[10]);} // unique_ptr<int[]> 使用 default_delete<int[]>// default_delete 能用于需要删除用函数对象的任何场所std::vector<int*> v;for(int n = 0; n < 100; ++n)v.push_back(new int(n));std::for_each(v.begin(), v.end(), std::default_delete<int>());
}#endif //_DEFAULT_DELETE_TEST_H_
五、智能指针不是灵丹妙药
5.1 何时使用智能指针
有三种典型的情况适合使用智能指针:
- 资源所有权的共享
共享所有权是指两个或多个对象需要同时使用第某个对象的情况。这个对象应该如何(或者说何时)被释放?为了确保释放的时机是正确的,每个使用这个共享资源的对象必须互相知道对方,才能准确掌握资源的释放时间。从设计或维护的观点来看,这种耦合是不可行的。更好的方法是让这些资源所有者将资源的生存期管理责任委派给一个智能指针。当没有共享者存在时,智能指针就可以安全地释放这个资源了。
- 要写异常安全的代码时
异常安全,简单地说就是在异常抛出时没有资源泄漏并保证程序状态的一致性。如果一个对象是动态分配的,当异常抛出时它不会被删除。由于栈展开以及指针离开作用域,资源可以会泄漏直至程序结束(即使是程序结束时的资源回收也不是语言所保证的)。不仅可能程序会由于内存泄漏而耗尽资源,程序的状态也可能变得混乱。智能指针可以自动地为你释放这些资源,即使是在异常发生的情况下。
- 避免常见的错误,如资源泄漏
避免常见的错误。忘记调用 delete 的错误,尤其是在复杂业务逻辑情况下。一个智能指针不关心程序中的控制路径;它只关心在它所指向的对象的生存期结束时删除它。使用智能指针,你不再需要知道何时删除对象。并且,智能指针隐藏了释放资源的细节,因此使用者不需要知道是否要调用 delete, 有些特殊的清除函数并不总是删除资源的。
5.2 有些时候要避免智能指针
使用裸指针来写异常安全和无错误的代码是很复杂的。使用智能指针来自动地把动态分配对象的生存期限制在一个明确的范围之内,是解决这种问题的一个有效的方法,并且提高了代码的可读性、可维护性和质量。
其一,智能指针并不智能,还是需要人去精心控制,对于一些大量对象存储以及循环遍历的应用来说,智能指针带来的性能损耗显然易见的,且看下面的例子:
#ifndef _RUN_TEST_H_
#define _RUN_TEST_H_#include <vector>
#include <iostream>
#include <memory>
#include <time.h>class AClass
{
private:int val;
public:AClass(const int &val_);~AClass();void setVal(const int &val_);
};AClass::AClass(const int &val_) : val(val_){ }AClass::~AClass(){ }void AClass::setVal(const int &val_)
{val = val_;
}const int VSIZE = 10000000;
void func12(void)
{std::vector<AClass*> vecs;for (size_t i = 0; i < VSIZE; i++){vecs.push_back(new AClass(i%10));}for (size_t i = 0; i < VSIZE; i++){vecs[i]->setVal(i%100);}for (size_t i = 0; i < VSIZE; i++){delete vecs[i];vecs[i] = NULL;}vecs.clear();
}void func13(void)
{std::vector<std::unique_ptr<AClass> > vecs;for (size_t i = 0; i < VSIZE; i++){vecs.push_back(std::unique_ptr<AClass>(new AClass(i%10)));}for (size_t i = 0; i < VSIZE; i++){vecs[i]->setVal(i%100);}
}#endif
//main.cppstd::cout << "clock() 1= " << clock() << "\n";func12();std::cout << "clock() 2= " << clock() << "\n";func13();std::cout << "clock() 3= " << clock() << "\n";
//out log
clock() 1= 0
clock() 2= 1355
clock() 3= 7041
win和linux输出:
其二,避免滥用智能指针。对于一些简单、直观、易懂的业务逻辑实现,引入智能指针反而影响其可阅读性、执行效率,其实开发者如果能有效进行内存管理,就不必要引入智能指针。
其三,各家使用智能指针差别挺大的,智能指针的传染性很大,涉及到跨新老款库、第三方库等归一及兼容问题时,要替换的话,就要替换所有的指针,风险很大、工程很大。
5.3 c++智能指针还不成熟稳定
智能指针在C++各个版本中不断地又增删,说明C++标准委员会对于智能指针在c++的引用也是不断权衡中,像c++14、 17、 20、 23都对智能指针的成员变量及成员函数作出了调整,c++23还增加了智能指针适配器。
智能指针适配器(C++23)
out_ptr_t 与外来指针设置器交互,并在析构时重设智能指针(类模板)
out_ptr 以关联的智能指针和重设参数创建 out_ptr_t (函数模板)
inout_ptr_t 与外来指针设置器交互,从智能指针获得初始指针值,并在析构时重设它(类模板)
inout_ptr 以关联的智能指针和重设参数创建 inout_ptr_t(函数模板)
使用智能指针,优先考虑使用unique_ptr
,因为它没有开销,相对成熟,是用来替代旧的auto_ptr的,当使用shared_ptr
时,则要问题自己,是否非他不可,采用它是否能带来实际性改善,否则不建议使用。
六、附录-各智能指针类详细声明代码
6.1标准库中类模板 std::unique_ptr的详细声明
namespace std {template<class T, class D = default_delete<T>> class unique_ptr {public:using pointer = /* 见描述 */;using element_type = T;using deleter_type = D;// 构造函数constexpr unique_ptr() noexcept;explicit unique_ptr(pointer p) noexcept;unique_ptr(pointer p, /* 见描述 */ d1) noexcept;unique_ptr(pointer p, /* 见描述 */ d2) noexcept;unique_ptr(unique_ptr&& u) noexcept;constexpr unique_ptr(nullptr_t) noexcept;template<class U, class E>unique_ptr(unique_ptr<U, E>&& u) noexcept;// 析构函数~unique_ptr();// 赋值unique_ptr& operator=(unique_ptr&& u) noexcept;template<class U, class E>unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;unique_ptr& operator=(nullptr_t) noexcept;// 观察器add_lvalue_reference_t<T> operator*() const noexcept(/* 见描述 */);pointer operator->() const noexcept;pointer get() const noexcept;deleter_type& get_deleter() noexcept;const deleter_type& get_deleter() const noexcept;explicit operator bool() const noexcept;// 修改器pointer release() noexcept;void reset(pointer p = pointer()) noexcept;void swap(unique_ptr& u) noexcept;// 禁用从左值复制unique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;};template<class T, class D> class unique_ptr<T[], D> {public:using pointer = /* 见描述 */;using element_type = T;using deleter_type = D;// 构造函数constexpr unique_ptr() noexcept;template<class U> explicit unique_ptr(U p) noexcept;template<class U> unique_ptr(U p, /* 见描述 */ d) noexcept;template<class U> unique_ptr(U p, /* 见描述 */ d) noexcept;unique_ptr(unique_ptr&& u) noexcept;template<class U, class E>unique_ptr(unique_ptr<U, E>&& u) noexcept;constexpr unique_ptr(nullptr_t) noexcept;// 析构函数~unique_ptr();// 赋值unique_ptr& operator=(unique_ptr&& u) noexcept;template<class U, class E>unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;unique_ptr& operator=(nullptr_t) noexcept;// 观察器T& operator[](size_t i) const;pointer get() const noexcept;deleter_type& get_deleter() noexcept;const deleter_type& get_deleter() const noexcept;explicit operator bool() const noexcept;// 修改器pointer release() noexcept;template<class U> void reset(U p) noexcept;void reset(nullptr_t = nullptr) noexcept;void swap(unique_ptr& u) noexcept;// 禁用从左值复制unique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;};
}
6.2 类模板 std::shared_ptr
namespace std {template<class T> class shared_ptr {public:using element_type = remove_extent_t<T>;using weak_type = weak_ptr<T>;// 构造函数constexpr shared_ptr() noexcept;constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { }template<class Y>explicit shared_ptr(Y* p);template<class Y, class D>shared_ptr(Y* p, D d);template<class Y, class D, class A>shared_ptr(Y* p, D d, A a);template<class D>shared_ptr(nullptr_t p, D d);template<class D, class A>shared_ptr(nullptr_t p, D d, A a);template<class Y>shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;template<class Y>shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept;shared_ptr(const shared_ptr& r) noexcept;template<class Y>shared_ptr(const shared_ptr<Y>& r) noexcept;shared_ptr(shared_ptr&& r) noexcept;template<class Y>shared_ptr(shared_ptr<Y>&& r) noexcept;template<class Y>explicit shared_ptr(const weak_ptr<Y>& r);template<class Y, class D>shared_ptr(unique_ptr<Y, D>&& r);// 析构函数~shared_ptr();// 赋值shared_ptr& operator=(const shared_ptr& r) noexcept;template<class Y>shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;shared_ptr& operator=(shared_ptr&& r) noexcept;template<class Y>shared_ptr& operator=(shared_ptr<Y>&& r) noexcept;template<class Y, class D>shared_ptr& operator=(unique_ptr<Y, D>&& r);// 修改器void swap(shared_ptr& r) noexcept;void reset() noexcept;template<class Y>void reset(Y* p);template<class Y, class D>void reset(Y* p, D d);template<class Y, class D, class A>void reset(Y* p, D d, A a);// 观察器element_type* get() const noexcept;T& operator*() const noexcept;T* operator->() const noexcept;element_type& operator[](ptrdiff_t i) const;long use_count() const noexcept;explicit operator bool() const noexcept;template<class U>bool owner_before(const shared_ptr<U>& b) const noexcept;template<class U>bool owner_before(const weak_ptr<U>& b) const noexcept;};template<class T>shared_ptr(weak_ptr<T>) -> shared_ptr<T>;template<class T, class D>shared_ptr(unique_ptr<T, D>) -> shared_ptr<T>;
}
6.3 类模板 std::weak_ptr
namespace std {template<class T> class weak_ptr {public:using element_type = remove_extent_t<T>;// 构造函数constexpr weak_ptr() noexcept;template<class Y>weak_ptr(const shared_ptr<Y>& r) noexcept;weak_ptr(const weak_ptr& r) noexcept;template<class Y>weak_ptr(const weak_ptr<Y>& r) noexcept;weak_ptr(weak_ptr&& r) noexcept;template<class Y>weak_ptr(weak_ptr<Y>&& r) noexcept;// 析构函数~weak_ptr();// 赋值weak_ptr& operator=(const weak_ptr& r) noexcept;template<class Y>weak_ptr& operator=(const weak_ptr<Y>& r) noexcept;template<class Y>weak_ptr& operator=(const shared_ptr<Y>& r) noexcept;weak_ptr& operator=(weak_ptr&& r) noexcept;template<class Y>weak_ptr& operator=(weak_ptr<Y>&& r) noexcept;// 修改器void swap(weak_ptr& r) noexcept;void reset() noexcept;// 观察器long use_count() const noexcept;bool expired() const noexcept;shared_ptr<T> lock() const noexcept;template<class U>bool owner_before(const shared_ptr<U>& b) const noexcept;template<class U>bool owner_before(const weak_ptr<U>& b) const noexcept;};template<class T>weak_ptr(shared_ptr<T>) -> weak_ptr<T>;
}
6.4 四个辅助类
namespace std {template<class T = void> struct owner_less;template<class T> struct owner_less<shared_ptr<T>> {bool operator()(const shared_ptr<T>&, const shared_ptr<T>&) const noexcept;bool operator()(const shared_ptr<T>&, const weak_ptr<T>&) const noexcept;bool operator()(const weak_ptr<T>&, const shared_ptr<T>&) const noexcept;};template<class T> struct owner_less<weak_ptr<T>> {bool operator()(const weak_ptr<T>&, const weak_ptr<T>&) const noexcept;bool operator()(const shared_ptr<T>&, const weak_ptr<T>&) const noexcept;bool operator()(const weak_ptr<T>&, const shared_ptr<T>&) const noexcept;};template<> struct owner_less<void> {template<class T, class U>bool operator()(const shared_ptr<T>&, const shared_ptr<U>&) const noexcept;template<class T, class U>bool operator()(const shared_ptr<T>&, const weak_ptr<U>&) const noexcept;template<class T, class U>bool operator()(const weak_ptr<T>&, const shared_ptr<U>&) const noexcept;template<class T, class U>bool operator()(const weak_ptr<T>&, const weak_ptr<U>&) const noexcept;using is_transparent = /* 未指明 */;};
}namespace std {template<class T> class enable_shared_from_this {protected:constexpr enable_shared_from_this() noexcept;enable_shared_from_this(const enable_shared_from_this&) noexcept;enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept;~enable_shared_from_this();public:shared_ptr<T> shared_from_this();shared_ptr<T const> shared_from_this() const;weak_ptr<T> weak_from_this() noexcept;weak_ptr<T const> weak_from_this() const noexcept;private:mutable weak_ptr<T> weak_this; // 仅用于阐释};
}namespace std {class bad_weak_ptr : public exception {public:bad_weak_ptr() noexcept;};
}namespace std {template<class T> struct default_delete {constexpr default_delete() noexcept = default;template<class U> default_delete(const default_delete<U>&) noexcept;void operator()(T*) const;};template<class T> struct default_delete<T[]> {constexpr default_delete() noexcept = default;template<class U> default_delete(const default_delete<U[]>&) noexcept;template<class U> void operator()(U* ptr) const;};
}
相关文章:

C/C++开发,无可避免的内存管理(篇四)-智能指针备选
一、智能指针 采用C/C开发堆内存管理无论是底层开发还是上层应用,无论是开发新手,还是多年的老手,都会不自觉中招,尤其是那些不是自己一手经历的代码,要追溯问题出在哪里更是个麻烦事。C/C程序常常会遇到程序突然退出&…...

VMware ESXi给虚拟机扩容
用ESXi管理的虚拟机硬盘空间不够了,讲一下如何进行扩容。 一、查看现状 通过如下三个命令,可以查看硬盘情况,可以看到只有500G,已经用了45%。这次我们再扩容500G。 df -Th lsblk fdisk -lIDE磁盘的文件名为 /de…...

认识STM32和如何构建STM32工程
STM32介绍什么是单片机单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种/0口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电…...

RabbitMQ延迟队列
目录 一、概念 二、使用场景 三、RabbitMQ 中的 TTL (一)队列设置 TTL (二)消息设置 TTL (三)两者的区别 四、整合SpringBoot实现延迟队列 (一)创建项目 (二&am…...

Java中常用的七种队列你了解多少?
文章目录Java中常用的七种队列你了解多少?ArrayBlockingQueue队列如何使用?添加元素到队列获取队列中的元素遍历队列LinkedBlockingQueue队列如何使用?1. 创建SynchronousQueue对象2. 添加元素到队列3. 获取队列中的元素4. 遍历队列SynchronousQueue队列…...
<Java获取时间日期工具类>常见八种场景(一)
一:自定义时间日期工具类常用的八种方式(整理): 0,getTimeSecondNum:时间日期转成秒数,常用于大小比较 1,getLastYearMonthLastDay:获取去年当月最后一天的时间日期 2,getLastYearM…...
接上一篇 对多个模型环形旋转进行优化 指定旋转位置
using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; public class ModelAnimal : MonoBehaviour { //记录鼠标滑动 public Vector2 lastPos;//鼠标上次位置 Vector2 currPos;//鼠标当前位置 Vector2 offset;//两次位置的偏移…...

Unity中获取地形的法线
序之前,生成了地形图:(42条消息) 从灰度图到地形图_averagePerson的博客-CSDN博客那末,地形的法线贴图怎么获取?大概分为两个部分吧,先拿到法线数据,再画到纹理中去。关于法线计算Unity - Scripting API: M…...

模型解释性:PFI、PDP、ICE等包的用法
本篇主要介绍几种其他较常用的模型解释性方法。 1. Permutation Feature Importance(PFI) 1.1 算法原理 置换特征重要性(Permutation Feature Importance)的概念很简单,其衡量特征重要性的方法如下:计算特征改变后模型预测误差的增加。如果打乱该特征的…...
spring常见面试题(2023最新)
目录前言1.spring是什么2.spring的设计核心是什么3.IOC和AOP面试题4.spring的优点和缺点5.spring中bean的作用域6.spring中bean的注入方式7.BeanFactory 和 ApplicationContext有什么区别?8.循环依赖的情况,怎么解决?9.spring中单例Bean是线程…...

华为OD机试题,用 Java 解【压缩报文还原】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...

机器学习-BM-FKNCN、BM-FKNN等分类器对比实验
目录 一、简介和环境准备 二、算法简介 2.1四种方法类: 2.1.1FKNN 2.1.2FKNCN 2.1.3BM-FKNN 2.1.3BM-FKNCN 2.2数据预处理 2.3输出视图 2.4调用各种方法看准确率 2.4.1BM-FKNCN 2.4.2BM-FKNN 2.4.3FKNCN 2.4.4FKNN 2.4.5KNN 一、简介和环境准备 k…...

ChatGPT火了,对话式人工智能还能干嘛?
身兼数职的ChatGPT 从2022火到了2023 连日来一直是各大平台的热议对象 其实除了写诗、敲代码、处理文档 以ChatGPT为代表的 对话式人工智能 还有更重要的工作要做 对话式AI与聊天机器人 相信大多数人…...
十一、操作数栈的特点(Operand Sstack)
1.每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的操作数栈,也可以称之为表达式栈。 2.操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据,或提取数据,即入栈ÿ…...
拆解瑞幸新用户激活流程,如何让用户“动”起来?
Aha时刻 一个产品的拉新环节,是多种方式并存的;新用户可能来自于商务搭建了新的渠道,运营策划了新的活动,企划发布了新的广告,销售谈下了新的客户,市场推广了新的群体,以及产品本身的口碑传播,功能更新带来的自然流量。 这是一个群策群力的环节,不同的团队背负不同的K…...

tkinter界面的TCP通信/开启线程等待接收数据
前言 用简洁的语言写一个可以与TCP客户端实时通信的界面。之前做了一个项目是要与PLC进行信息交互的界面,在测试的时候就利用TCP客户端来实验,文末会附上TCP客户端。本文分为三部分,第一部分是在界面向TCP发送数据,第二部分是接收…...

华为OD机试题,用 Java 解【任务混部】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...
看linux内核启动流程需要的汇编指令解释
一、指令 0.MRS 和MSR MRS 指令: 对状态寄存器CPSR和SPSR进行读操作。 MSR指令: 对状态寄存器CPSR和SPSR进行写操作。 1.adrp adrp x0, boot_args把boot_args的页基地址提取出来,放到x0中。 2.stp stp x21, x1, [x0]将 x21, x1 的值存入 x0寄存器记录的地址中…...

【巨人的肩膀】JAVA面试总结(二)
1、💪 目录1、💪1.0、什么是面向对象1.1、JDK、JRE、JVM之间的区别1.2、什么是字节码1.3、hashCode()与equals()之间的联系1.4、String、StringBuffer、StringBuilder的区别1.5、和equals方法的区别1.6、重载和重写的区别1.7、List和Set的区别1.8、Array…...

【网络安全入门】零基础小白必看!!!
看到很多小伙伴都想学习 网络安全 ,让自己掌握更多的 技能,但是学习兴趣有了,却发现自己不知道哪里有 学习资源◇瞬间兴致全无!◇ 😄在线找人要资料太卑微,自己上网下载又发现要收费0 🙃差点当…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...