0、差不多春节啦。。。。。 好久没有写博客,写一写吧。。。。。。 祝大家嗨皮,提前恭喜发财
1、三种智能指针的使用方法
C++ 有3种指针:share_ptr, unique_ptr, weak_ptr
1.1)unique_ptr 指针
std::unique_ptr
是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。【拷贝构造函数、赋值重载运算符是private or deleted的】
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
创建unique_ptr 指针的3种方式:
// 1.使用reset方法
unique_ptr<SmartPtrClass> smart1;
smart1.reset(new SmartPtrClass("smart1"));
// 2. 使用make_unique 方法,C++14支持。括号里的是参数
unique_ptr<SmartPtrClass> smart3 = make_unique<SmartPtrClass>("smart3");
// 3. () 方法
unique_ptr<SmartPtrClass> smart2(new SmartPtrClass("smart2"));
unique_ptr 不能赋值或者调用复制构造函数,但是可以调用移动构造函数,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。【下列ptrA 指针将变为空指针】
class SmartPtrClass{
private:
string className;
public:
SmartPtrClass(string m_name):className(m_name){
cout << className << " constructor" << endl;
}
~SmartPtrClass(){
cout << className << " destructor" << endl;
}
void show(){
cout << "this is object: " << className << " show" << endl;
}
};
int main(){
unique_ptr<SmartPtrClass> smart2(new SmartPtrClass("smart2"));
(*smart2).show();
// 方法3创建对象,括号的是参数
unique_ptr<SmartPtrClass> smart3 = make_unique<SmartPtrClass>("smart3");
smart3->show();
// smart2释放所有权给 tmp,但是 smart2不会调用析构函数,只能通过 delete tmp1
SmartPtrClass* tmp1 = smart2.release();
// 可以通过move 函数将 smart2 转移给 tmp,此时可以释放析构函数
unique_ptr<SmartPtrClass> tmp2(std::move(smart3));
// 无法再调用 smart2,会运行错误;smart2 已经是空指针了,会打印下列的log
if (!smart2){
cout << "smart2 pointer is null =======" << endl;
}
if (!smart3){
cout << "smart3 pointer is null ========" << endl;
}
cout << "===========testSmartPtr===========" << endl;
}
// 输出结果:
smart2 constructor
this is object: smart2 show
smart3 constructor
this is object: smart3 show
smart2 pointer is null =======
smart3 pointer is null ========
===========testSmartPtr===========
smart3 destructor
由上可知:
通过 release 方法释放所有权、通过移动语义std::move转移所有权的区别:
1. release 方法不能调用析构函数【需要通过delete】;move可以调用析构函数
相同点:
源指针变为空指针
auto_ptr 指针被废弃的原因
auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation;
// 主要是因为下列赋值语句可以成功,拷贝构造函数不是private 的
vocaticn = ps;
上述赋值语句将完成什么工作呢?如果 ps 和 vocation 是常规指针,则两个指针将指向同一个 string 对象。这是不能接受的,因为程序将试图删除同一个对象两次,一次是 ps 过期时,另一次是 vocation 过期时。
要避免这种问题,方法有多种:
- 定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
- 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。【转让了所有权,则源指针是为空指针的】
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加 1,而指针过期时,计数将减 1,。当减为 0 时才调用 delete。这是 shared_ptr 采用的策略。
unique_ptr 比 auto_ptr 更加安全,因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用 std::move() 进行控制权限的转移,如下代码所示:
unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt); //编译出错,已禁止拷贝
unique_ptr<string> upt1=upt; //编译出错,已禁止拷贝
unique_ptr<string> upt1=std::move(upt); //控制权限转移, upt1 为空指针
auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt); //编译通过
auto_ptr<string> apt1=apt; //编译通过
1.2)shared_ptr 指针
shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr最初实现于Boost库中,后由 C++11 引入到 C++ STL。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;
(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
3种创建 shared_ptr 指针的方法:
// 1. () 括号形式
shared_ptr<SmartPtrClass> shSmart1(new SmartPtrClass("shSmart1"));
// 2. make_shared形式
shared_ptr<SmartPtrClass> shSmart2 = make_shared<SmartPtrClass>("shSmart2");
// 3. reset 方法
shared_ptr<SmartPtrClass> shSmart3;
shSmart3.reset(new SmartPtrClass("shSmart3"));
shSmart3->show();
通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数, 并通过use_count()来查看一个对象的引用计数
// 可以直接使用赋值运算
shared_ptr<SmartPtrClass> shTmp1 = shSmart1;
auto shTmp2 = shSmart1;
cout << "shSmart1 ref cout: " << shSmart1.use_count() << endl;
shTmp1.reset();
cout << "shSmart1 ref cout afther reset: " << shSmart1.use_count() << endl;
auto orig = shTmp2.get();
cout << "===========testSmartPtr===========" << endl;
//输出log:
shSmart1 ref cout: 3
shSmart1 ref cout afther reset: 2
===========testSmartPtr===========
1.3)weak_ptr 指针
std::weak_ptr
是一种弱引用(相比较而言 std::shared_ptr
就是一种强引用)。弱引用不会引起引用计数增加。weak_ptr 为解决循环引用的问题,如下代码:
struct A;
struct B;
struct A {
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
struct B {
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
}
运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 a,b
,这使得 a,b
的引用计数均变为了 2,而离开作用域时,a,b
智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 a,b
对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,
解决这个问题的办法就是使用弱引用指针 std::weak_ptr
,std::weak_ptr
是一种弱引用(相比较而言 std::shared_ptr
就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图 5.2 所示:
struct A;
struct B;
struct A {
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
struct B {
// 使用 weak 指针
std::weak_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
// weak_ptr管理的资源不能被直接访问,需要通过lock函数从其中获取shared_ptr才可以被访问。
std::shared_ptr<A> tempA = b->pointer.lock();
}
1.4)智能指针使用的场景
(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
- 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
- 两个对象包含都指向第三个对象的指针;
- STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。
如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。
2. 实现 unique_ptr 智能指针
智能指针的实现原理:
利用在栈上创建的类,其会自动调用析构函数。创建一个包装类,包装对应的指针,重载运算符:* 与 -> 让其对象像是在指针上操作。该类在栈上创建,当对象不再使用时,自动析构,在析构函数中delete 对应包装的指针
实现需要注意的点:
1. 构造函数的隐式变换
2. 编写模板类
3. 禁止拷贝构造函数和 赋值构造函数
4. 移动构造、移动赋值重载运算符
5. 判断对象是否存在 ,重载 operator bool (),explicit关键字不能少
6. 如果未标记noexcept,标准库将产生一个额外的操作,增加了开销。
7. 使用 std::swap 提高运行速度
// 实现一个 unique_ptr 智能指针, T 是传入的对象
template <typename T>
class UniquePtr{
private:
T* ptr = nullptr;
public:
// 拷贝构造函数和 赋值构造函数是不能使用的
UniquePtr(const UniquePtr& copy) noexcept = delete;
// 赋值构造函数 不能使用
UniquePtr& operator = (const UniquePtr& copy) noexcept = delete;
// 交换 2 个指针
void swap(UniquePtr<T>& src){
std::swap(ptr, src.ptr);
}
// 移动所有权是可以使用的,移动构造函数和移动的赋值重载运算符是可以使用的
UniquePtr(UniquePtr&& uptr) noexcept {
cout << "UniquePtr move construtor" << endl;
// 或者直接使用下列swap函数
// uptr.swap(*this);
ptr = uptr.ptr;
delete uptr.ptr;
uptr.ptr = nullptr;
}
// 移动构造函数和移动的赋值重载运算符
UniquePtr& operator = (const UniquePtr&& uptr) noexcept{
cout << "UniquePtr move UniquePtr& operator = " << endl;
// 或者直接使用下列swap函数
// uptr.swap(*this);
if (*this != uptr){
if (ptr)
delete ptr;
ptr = uptr.ptr;
delete uptr.ptr;
uptr.ptr = nullptr;
}
return *this;
}
// 析构函数删除传入的指针
~UniquePtr(){
cout << "UniquePtr destructor" << endl;
delete ptr;
}
// 构造函数, 需要使用隐式
explicit UniquePtr(T* _ptr){
cout << "UniquePtr costructor" << endl;
ptr = std::move(_ptr);
}
UniquePtr():ptr(nullptr){
cout << "UniquePtr costructor. params nullptr" << endl;
}
// 用于判断对象是否存在,explicit关键字不能少
explicit operator bool () noexcept{
cout << "UniquePtr operator bool" << endl;
return this->ptr;
}
// ========== release, get, reset 方法 ==========
T* release() noexcept{
cout << "UniquePtr release" << endl;
// 需要先缓存前面的 指针
T* tmp = ptr;
delete ptr;
ptr = nullptr;
return tmp;
}
T* get() noexcept{
return ptr;
}
void reset (T* srcPtr) noexcept{
delete ptr;
ptr = nullptr;
std::swap(ptr, srcPtr);
}
// ========== 重载 * 和 -> ==========
T* operator -> () noexcept{
return this->ptr;
}
T& operator * () noexcept{
return *this->ptr;
}
};
class TestClass{
private:
int value = 3;
public:
TestClass(){
cout << "TestClass costructor" << endl;
}
~TestClass(){
cout << "TestClass destructor" << endl;
}
void show(){
cout << "TestClass show======" << endl;
}
void showValue(){
cout << "TestClass showValue======" << value << endl;
}
};
// 实现一个 shared_ptr 智能指针
void testSmartPointer(){
cout << "=========testSmartPointer=========" << endl;
UniquePtr<TestClass> tc(new TestClass);
// UniquePtr<TestClass> tc2(tc);
// UniquePtr<TestClass> tc2 = tc;
tc->show();
(*tc).show();
UniquePtr<TestClass> tc1(std::move(tc));
tc1->show();
// tc 还是有所有权
tc->show();
// 如果需要判断,则必须重载 operator bool
if (tc){
cout << "tc is not NULL" << endl;
}
// value 资源不可用,程序崩溃
// tc->showValue();
UniquePtr<TestClass> tc3;
tc3.reset(new TestClass);
tc3->show();
cout << "=========testSmartPointer=========" << endl;
}
3. 实现 share_ptr 智能指针
首先先明确的一点:为什么赋值运算符重载需要先减去 1
1. 对于对SharedPtr 的重新赋值,需要调用指针的析构函数
2. 指针的拷贝形式是:浅拷贝形式,两个count 值是相同的
// 重载赋值 = 运算符,需要ref count 减去1
SharedPtr<PointerType>& operator = (const SharedPtr<PointerType>& src) noexcept{
cout << "SharedPtr operator = " << endl;
if (this == &src){
return *this;
}
if (counter->reduceRef() == 0){
delete ptr;
delete counter;
cout << "重新赋值,删除原始的指针========= " << endl;
}
ptr = src.ptr;
// 浅拷贝形式,两个count 值是相同的
counter = src.counter;
// 增加一个计数,
counter->addRef();
return *this;
}
// why: 为什么重载 operator =, 使用if (counter->reduceRef() == 0)
SharedPtr<TestClass> s5(new TestClass);
cout << "before s5 ref cout: " << s5.use_count() << endl;
SharedPtr<TestClass> s6(new TestClass);
s5 = s6;
// 浅拷贝形式,两个count 值是相同的
cout << "afther s5 ref cout: " << s5.use_count() << endl;
cout << "afther s6 ref cout: " << s6.use_count() << endl;
// ================= shared_ptr 类库 =================
shared_ptr<TestClass> ptr1(new TestClass);
shared_ptr<TestClass> ptr2(new TestClass);
cout << "before shared_ptr ref cout: " << ptr1.use_count() << endl;
// 会先调用下 TestClass 的析构函数
ptr1 = ptr2;
cout << "afther shared_ptr ref cout: " << ptr1.use_count() << endl;
cout << "afther shared_ptr ref cout: " << ptr2.use_count() << endl;
// =======输出:
TestClass costructor
SharedPtr costructor
before s5 ref cout: 1
TestClass costructor
SharedPtr costructor
SharedPtr operator =
TestClass destructor
重新赋值,删除原始的指针=========
afther s5 ref cout: 2
afther s6 ref cout: 2
------
TestClass costructor
TestClass costructor
before shared_ptr ref cout: 1
TestClass destructor
afther shared_ptr ref cout: 2
afther shared_ptr ref cout: 2
共享 shared 智能指针需要注意的几个点:
1. = 赋值重载运算符需要先减去 1 个计算
2. 在拷贝构造函数 和 赋值运算符去 增加 1 个计数引用
3. 在析构函数 和 赋值重载运算符去 减去 1 个计数引用
4. 如 unique 指针,在构造函数 增加隐式声明,noexcept。
class RefCounter{
private:
long count;
public:
RefCounter(long _count){
count = _count;
}
long reduceRef(){
// c++标准库中的操作是一个原子操作,且要保证线程安全(如加锁操作)
return --count;
}
void addRef(){
++count;
}
long getCount(){
return count;
}
};
// 实现一个 shared_ptr 智能指针
template <typename PointerType>
class SharedPtr{
private:
PointerType* ptr;
RefCounter* counter;
public:
explicit SharedPtr(PointerType* _ptr = nullptr){
cout << "SharedPtr costructor" << endl;
ptr = _ptr;
if (ptr){
counter = new RefCounter(1);
}else{
counter = new RefCounter(0);
}
}
~SharedPtr(){
if (counter->reduceRef() == 0){
cout << "SharedPtr destructor" << endl;
delete ptr;
delete counter;
}else{
cout << "SharedPtr not destructor" << endl;
}
}
// 重载赋值 = 运算符,需要ref count 减去1
SharedPtr<PointerType>& operator = (const SharedPtr<PointerType>& src) noexcept{
cout << "SharedPtr operator = " << endl;
if (this == &src){
return *this;
}
if (counter->reduceRef() == 0){
delete ptr;
delete counter;
}
ptr = src.ptr;
counter = src.counter;
// 增加一个计数
counter->addRef();
return *this;
}
// 拷贝构造函数
SharedPtr(const SharedPtr<PointerType>& copy){
cout << "SharedPtr copy constructor " << endl;
if (this != ©){
ptr = copy.ptr;
counter = copy.counter;
counter->addRef();
}
}
// get函数
PointerType* get() noexcept{
return ptr;
}
// release 函数
void release(){
counter->reduceRef();
}
// use_count 函数
long use_count() noexcept{
return counter->getCount();
}
// 转换符 bool
explicit operator bool () noexcept {
return ptr;
}
// 重载 * 和 -> 预算符
PointerType* operator -> () noexcept{
return ptr;
}
PointerType operator * () noexcept{
return *ptr;
}
};