当前位置: 首页 > 工具软件 > smart-ioc > 使用案例 >

【Smart_Point】C/C++ 中智能指针

萧胜
2023-12-01

C++11智能指针

目录

C++11智能指针

1.1 C++11智能指针介绍

1.2 为什么要使用智能指针

1.2.1 auto_ptr(C++98的方案,C++11已经抛弃)采用所有权模式。

1.2.2 unique_ptr

1.2.3 shared_ptr

1.2.4 weak_ptr

1.3 share_ptr和weak_ptr的核心实现

1.3.1 Counter简单实现

1.3.2 share_ptr的简单实现

1.3.3 weak_ptr简单实现

1.4 分清楚场合应该使用哪种类型的智能指针;


C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。

1.1 C++11智能指针介绍

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

1.2 为什么要使用智能指针

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

1.2.1 auto_ptr(C++98的方案,C++11已经抛弃)采用所有权模式。

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

std::auto_ptr 真正让人容易误用的地方是其不常用的复制语义,即当复制一个 std::auto_ptr 对象时(拷贝复制或 operator = 复制),原对象所持有的堆内存对象也会转移给复制出来的对象。示例代码如下:

#include <iostream>
#include <memory>
​
int main()
{
    //测试拷贝构造
    std::auto_ptr<int> sp1(new int(8));
    std::auto_ptr<int> sp2(sp1);
    if (sp1.get() != NULL)
    {
        std::cout << "sp1 is not empty." << std::endl;
    }
    else
    {
        std::cout << "sp1 is empty." << std::endl;
    }
​
    if (sp2.get() != NULL)
    {
        std::cout << "sp2 is not empty." << std::endl;
    }
    else
    {
        std::cout << "sp2 is empty." << std::endl;
    }
​
    //测试赋值构造
    std::auto_ptr<int> sp3(new int(8));
    std::auto_ptr<int> sp4;
    sp4 = sp3;
    if (sp3.get() != NULL)
    {
        std::cout << "sp3 is not empty." << std::endl;
    }
    else
    {
        std::cout << "sp3 is empty." << std::endl;
    }
​
    if (sp4.get() != NULL)
    {
        std::cout << "sp4 is not empty." << std::endl;
    }
    else
    {
        std::cout << "sp4 is empty." << std::endl;
    }
​
    return 0;
}

上述代码中分别利用拷贝构造(sp1 => sp2)和 赋值构造(sp3 => sp4)来创建新的 std::auto_ptr 对象,因此 sp1 持有的堆对象被转移给 sp2,sp3 持有的堆对象被转移给 sp4。我们得到程序执行结果如下:

[root@iZ238vnojlyZ testx]# g++ -g -o test_auto_ptr test_auto_ptr.cpp
[root@iZ238vnojlyZ testx]# ./test_auto_ptr 
sp1 is empty.
sp2 is not empty.
sp3 is empty.
sp4 is not empty.

由于 std::auto_ptr 这种不常用的复制语义,我们应该避免在 stl 容器中使用 std::auto_ptr,例如我们绝不应该写出如下代码:

std::vector<std::auto_ptr<int>> myvectors;

当用算法对容器操作的时候(如最常见的容器元素遍历),很难避免不对容器中的元素实现赋值传递,这样便会使容器中多个元素被置为空指针,这不是我们想看到的,会造成很多意想不到的错误。

以史为鉴,作为 std::auto_ptr 的替代者 std::unique_ptr 吸取了这个经验教训。下文会来详细介绍。

正因为 std::auto_ptr 的设计存在如此重大缺陷,C++11 标准在充分借鉴和吸收了 boost 库中智能指针的设计思想,引入了三种类型的智能指针,即 std::unique_ptrstd::shared_ptrstd::weak_ptr

boost 还有 scoped_ptr,C++11 并没有全部照搬,而是选择了三个最实用的指针类型。在 C++11 中可以通过 std::unique_ptr 达到与 boost::scoped_ptr 一样的效果。

1.2.2 unique_ptr

(替换auto_ptrunique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。 与shared_ptr不同,某一时刻,只能有一个unique_ptr指向一个给定的对象。因此,当unique_ptr被销毁,它所指的对象也会被销毁。

std::unique_ptr 对其持有的堆内存具有唯一拥有权,也就是说引用计数永远是 1,std::unique_ptr 对象销毁时会释放其持有的堆内存。可以使用以下方式初始化一个 std::unique_ptr 对象:

//初始化方式1
std::unique_ptr<int> sp1(new int(123));
​
//初始化方式2
std::unique_ptr<int> sp2;
sp2.reset(new int(123));
​
//初始化方式3
std::unique_ptr<int> sp3 = std::make_unique<int>(123);
你应该尽量使用初始化方式 3 的方式去创建一个 std::unique_ptr 而不是方式 1 和 2,因为形式 3 更安全,原因 Scott Meyers 在其《Effective Modern C++》中已经解释过了,有兴趣的读者可以阅读此书相关章节。

令很多人对 C++11 规范不满的地方是,C++11 新增了 std::make_shared() 方法创建一个 std::shared_ptr 对象,却没有提供相应的 std::make_unique() 方法创建一个 std::unique_ptr 对象,这个方法直到 C++14 才被添加进来。当然,在 C++11 中你很容易实现出这样一个方法来:

template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&& ...params)
{
    return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

鉴于 std::auto_ptr 的前车之鉴,std::unique_ptr 禁止复制语义,为了达到这个效果,std::unique_ptr 类的拷贝构造函数和赋值运算符(operator =)被标记为 delete

template <class T>
class unique_ptr
{
    //省略其他代码...
​
    //拷贝构造函数和赋值运算符被标记为delete
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
};//禁止拷贝复制和operattor=复制,允许所有权转移

因此,下列代码是无法通过编译的:

std::unique_ptr<int> sp1(std::make_unique<int>(123));;
​
//以下代码无法通过编译
//std::unique_ptr<int> sp2(sp1);
std::unique_ptr<int> sp3;
//以下代码无法通过编译
//sp3 = sp1;

禁止复制语义也存在特例,即可以通过一个函数返回一个 std::unique_ptr:

#include <memory>

std::unique_ptr<int> func(int val)
{
    std::unique_ptr<int> up(new int(val));
    return up;
}

int main()
{
    std::unique_ptr<int> sp1 = func(123);
    return 0;
}

上述代码从 func 函数中得到一个 std::unique_ptr 对象,然后返回给 sp1。

既然 std::unique_ptr 不能复制,那么如何将一个 std::unique_ptr 对象持有的堆内存转移给另外一个呢?答案是使用移动构造,示例代码如下

#include <memory>

int main()
{
    std::unique_ptr<int> sp1(std::make_unique<int>(123));

    std::unique_ptr<int> sp2(std::move(sp1));

    std::unique_ptr<int> sp3;
    sp3 = std::move(sp2);

    return 0;
}

以上代码利用 std::move 将 sp1 持有的堆内存(值为 123)转移给 sp2,再把 sp2 转移给 sp3。最后,sp1 和 sp2 不再持有堆内存的引用,变成一个空的智能指针对象。并不是所有的对象的 std::move 操作都有意义,只有实现了移动构造函数(Move Constructor)或移动赋值运算符(operator =)的类才行,而 std::unique_ptr 正好实现了这二者,以下是实现伪码:

template<typename T, typename Deletor>
class unique_ptr
{
    //其他函数省略...
public:
    unique_ptr(unique_ptr&& rhs)
    {
        this->m_pT = rhs.m_pT;
        //源对象释放
        rhs.m_pT = nullptr;
    }

    unique_ptr& operator=(unique_ptr&& rhs)
    {
        this->m_pT = rhs.m_pT;
        //源对象释放
        rhs.m_pT = nullptr;
        return *this;
    }

private:
    T*    m_pT;
};

这是 std::unique_ptr 具有移动语义的原因。

std::unique_ptr 不仅可以持有一个堆对象,也可以持有一组堆对象,示例如下:

#include <iostream>
#include <memory>

int main()
{
    //创建10个int类型的堆对象
    //形式1
    std::unique_ptr<int[]> sp1(new int[10]);

    //形式2
    std::unique_ptr<int[]> sp2;
    sp2.reset(new int[10]);
    //形式3
    std::unique_ptr<int[]> sp3(std::make_unique<int[]>(10));

    for (int i = 0; i < 10; ++i)
    {
        sp1[i] = i;
        sp2[i] = i;
        sp3[i] = i;
    }

    for (int i = 0; i < 10; ++i)
    {
        std::cout << sp1[i] << ", " << sp2[i] << ", " << sp3[i] << std::endl;
    }

    return 0;
}

程序执行结果如下:

0, 0, 0
1, 1, 1
2, 2, 2
3, 3, 3
4, 4, 4
5, 5, 5
6, 6, 6
7, 7, 7
8, 8, 8
9, 9, 9

std::shared_ptrstd::weak_ptr 也可以持有一组堆对象,用法与 std::unique_ptr 相同,下文不再赘述。

采用所有权模式,还是上面那个例子

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4;                        //#5
p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。尝试复制p3时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptrauto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                      // #1 不允许
unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You"));   // #2 允许

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr

注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。

额外:boost库的boost::scoped_ptr也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

1.2.3 shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

td::unique_ptr 对其持有的资源具有独占性,而 std::shared_ptr 持有的资源可以在多个 std::shared_ptr 之间共享,每多一个 std::shared_ptr 对资源的引用,资源引用计数将增加 1,每一个指向该资源的 std::shared_ptr 对象析构时,资源引用计数减 1,最后一个 std::shared_ptr 对象析构时,发现资源计数为 0,将释放其持有的资源。多个线程之间,递增和减少资源的引用计数是安全的。(注意:这不意味着多个线程同时操作 std::shared_ptr 引用的对象是安全的)。std::shared_ptr 提供了一个 use_count() 方法来获取当前持有资源的引用计数。除了上面描述的,std::shared_ptr 用法和 std::unique_ptr 基本相同。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

std::shared_ptr<int> sp1(new int(123));
//
std::shared_ptr<int> sp2;
sp2.reset(new int(123));
//
std::shared+ptr<int> sp3;
sp3=std::make_shared<int>(123);

std::unique_ptr 一样,你应该优先使用 std::make_shared 去初始化一个 std::shared_ptr 对象。

再来看另外一段代码:

#include <iostream>
#include <memory>

class A
{
public:
    A()
    {
        std::cout << "A constructor" << std::endl;
    }

    ~A()
    {
        std::cout << "A destructor" << std::endl;
    }
};

int main()
{
    {
        //初始化方式1
        std::shared_ptr<A> sp1(new A());

        std::cout << "sp1 use count: " << sp1.use_count() << std::endl;

        //初始化方式2
        std::shared_ptr<A> sp2(sp1);
        std::cout << "sp2 use count: " << sp1.use_count() << std::endl;

        sp2.reset();
        std::cout << "sp2 reset use count: " << sp1.use_count() << std::endl;

        {
            std::shared_ptr<A> sp3 = sp1;
            std::cout << "sp3 = sp1 use count: " << sp1.use_count() << std::endl;
        }

        std::cout << "use count: " << sp1.use_count() << std::endl;
    }

    return 0;
}
  • 上述代码 22 行 sp1 构造时,同时触发对象 A 的构造,因此 A 的构造函数会执行;

  • 此时只有一个 sp1 对象引用 22 行 new 出来的 A 对象(为了叙述方便,下文统一称之为资源对象 A),因此代码 24 行打印出来的引用计数值为 1

  • 代码 27 行,利用 sp1 拷贝一份 sp2,导致代码 28 行打印出来的引用计数为 2

  • 代码 30 行调用 sp2 的 reset() 方法,sp2 释放对资源对象 A 的引用,因此代码 31 行打印的引用计数值再次变为 1

  • 代码 34 行 利用 sp1 再次 创建 sp3,因此代码 35 行打印的引用计数变为 2

  • 程序执行到 36 行以后,sp3 出了其作用域被析构,资源 A 的引用计数递减 1,因此 代码 38 行打印的引用计数为 1

  • 程序执行到 39 行以后,sp1 出了其作用域被析构,在其析构时递减资源 A 的引用计数至 0,并析构资源 A 对象,因此类 A 的析构函数被调用。

    A constructor
    sp1 use count: 1
    sp2 use count: 2
    sp2 reset use count: 1
    sp3 = sp1 use count: 2
    use count: 1
    A destructor
    

所以整个程序的执行结果如下:

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 为 1)

swap 交换两个 `shared_ptr` 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如
shared_ptr<int> sp(new int(1)); 

spsp.get()是等价的。

share_ptr的简单例子:

int main()
{
	string *s1 = new string("s1");

	shared_ptr<string> ps1(s1);
	shared_ptr<string> ps2;
	ps2 = ps1;

	cout << ps1.use_count()<<endl;	//2
	cout<<ps2.use_count()<<endl;	//2
	cout << ps1.unique()<<endl;		//0

	string *s3 = new string("s3");
	shared_ptr<string> ps3(s3);

	cout << (ps1.get()) << endl;	//033AEB48
	cout << ps3.get() << endl;		//033B2C50
	swap(ps1, ps3);				//交换所拥有的对象
	cout << (ps1.get())<<endl;	//033B2C50
	cout << ps3.get() << endl;	//033AEB48

	cout << ps1.use_count()<<endl;		//1
	cout << ps2.use_count() << endl;	//2
	ps2 = ps1;
	cout << ps1.use_count()<<endl;		//2
	cout << ps2.use_count() << endl;	//2
	ps1.reset();	//放弃ps1的拥有权,引用计数的减少
	cout << ps1.use_count()<<endl;	//0
	cout << ps2.use_count()<<endl;	//1
}

1.2.4 weak_ptr

share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptrweak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr

class B;	//声明
class A
{
public:
	shared_ptr<B> pb_;
	~A()
	{
		cout << "A delete\n";
	}
};

class B
{
public:
	shared_ptr<A> pa_;
	~B()
	{
		cout << "B delete\n";
	}
};

void fun()
{
	shared_ptr<B> pb(new B());
	shared_ptr<A> pa(new A());
	cout << pb.use_count() << endl;	//1
	cout << pa.use_count() << endl;	//1
	pb->pa_ = pa;
	pa->pb_ = pb;
	cout << pb.use_count() << endl;	//2
	cout << pa.use_count() << endl;	//2
}

int main()
{
	fun();
	return 0;
}

可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb,改为weak_ptr`pb` ,运行结果如下:

1
1
1
2
B delete
A delete

这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:

shared_ptr<B> p = pa->pb_.lock();
p->print();

weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.

expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.

lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.

use_count 返回与 shared_ptr 共享的对象的引用计数.

reset 将 weak_ptr 置空.

weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

1.3 share_ptrweak_ptr的核心实现

weak_ptr的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr的赋值,构造,所以先把counter和share_ptr简单实现

1.3.1 Counter简单实现

class Counter
{
public:
    Counter() : s(0), w(0){};
    int s;	//share_ptr的引用计数
    int w;	//weak_ptr的引用计数
};

counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr的引用计数,w是weak_ptr的引用计数,当w为0时,删除Counter对象。

1.3.2 share_ptr的简单实现

template <class T>
class WeakPtr; //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用

template <class T>
class SharePtr
{
public:
    SharePtr(T *p = 0) : _ptr(p)
    {
        cnt = new Counter();
        if (p)
            cnt->s = 1;
        cout << "in construct " << cnt->s << endl;
    }
    ~SharePtr()
    {
        release();
    }

    SharePtr(SharePtr<T> const &s)
    {
        cout << "in copy con" << endl;
        _ptr = s._ptr;
        (s.cnt)->s++;
        cout << "copy construct" << (s.cnt)->s << endl;
        cnt = s.cnt;
    }
    SharePtr(WeakPtr<T> const &w)//为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用
    {
        cout << "in w copy con " << endl;
        _ptr = w._ptr;
        (w.cnt)->s++;
        cout << "copy w  construct" << (w.cnt)->s << endl;
        cnt = w.cnt;
    }
    SharePtr<T> &operator=(SharePtr<T> &s)
    {
        if (this != &s)
        {
            release();
            (s.cnt)->s++;
            cout << "assign construct " << (s.cnt)->s << endl;
            cnt = s.cnt;
            _ptr = s._ptr;
        }
        return *this;
    }
    T &operator*()
    {
        return *_ptr;
    }
    T *operator->()
    {
        return _ptr;
    }
    friend class WeakPtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值

protected:
    void release()
    {
        cnt->s--;
        cout << "release " << cnt->s << endl;
        if (cnt->s < 1)
        {
            delete _ptr;
            if (cnt->w < 1)
            {
                delete cnt;
                cnt = NULL;
            }
        }
    }

private:
    T *_ptr;
    Counter *cnt;
};

share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存。

1.3.3 weak_ptr简单实现

template <class T>
class WeakPtr
{
public: //给出默认构造和拷贝构造,其中拷贝构造不能有从原始指针进行构造
    WeakPtr()
    {
        _ptr = 0;
        cnt = 0;
    }
    WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt)
    {
        cout << "w con s" << endl;
        cnt->w++;
    }
    WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt)
    {
        cnt->w++;
    }
    ~WeakPtr()
    {
        release();
    }
    WeakPtr<T> &operator=(WeakPtr<T> &w)
    {
        if (this != &w)
        {
            release();
            cnt = w.cnt;
            cnt->w++;
            _ptr = w._ptr;
        }
        return *this;
    }
    WeakPtr<T> &operator=(SharePtr<T> &s)
    {
        cout << "w = s" << endl;
        release();
        cnt = s.cnt;
        cnt->w++;
        _ptr = s._ptr;
        return *this;
    }
    SharePtr<T> lock()
    {
        return SharePtr<T>(*this);
    }
    bool expired()
    {
        if (cnt)
        {
            if (cnt->s > 0)
            {
                cout << "empty" << cnt->s << endl;
                return false;
            }
        }
        return true;
    }
    friend class SharePtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值
    
protected:
    void release()
    {
        if (cnt)
        {
            cnt->w--;
            cout << "weakptr release" << cnt->w << endl;
            if (cnt->w < 1 && cnt->s < 1)
            {
                //delete cnt;
                cnt = NULL;
            }
        }
    }

private:
    T *_ptr;
    Counter *cnt;
};

weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。

不过感觉最后的SharePtr有个地方可以改进下: SharePtr在析构时释放_ptr和cnt,可能会导致释放cnt不成功。 代码里cnt的释放取决于cnt->w, 而cnt->w是否为0取决于WeakPtr的析构。 如果SharePtr先于WeakPtr析构, 此时cnt->w 大于0,SharePtr无法释放cnt

这个地方感觉可以不用判断cnt->w,直接delete cnt; cnt->s都为0了,不会有别的地方再使用管理的对象_ptr了,cnt的存在也就没意义了。

void release()
{
        cnt->s--;
        cout << "release " << cnt->s << endl;
        if (cnt->s < 1)
        {
            delete _ptr;
            if (cnt->w < 1)  //取消这个判断
            {
              delete cnt;
              cnt = NULL;
             }
        }
}

1.4 分清楚场合应该使用哪种类型的智能指针;

1.4.1 std::unique_ptr:

1 小巧、高速、具备只移型别的智能指针,对托管资源实施专属所有权语义。

2 默认地,资源析构采用delete运算符来实现,但可以指定自定义删除器。有状态的删除器和采用函数指针实现的删除器会增加std::unique_ptr型别的对象尺寸

3 将std::unique_ptr 转换成std::shared_ptr是容易实现的

1.4.2 std::shared_ptr:

1 提供方便的手段,实现了任意资源在共享所有权语义下进行生命周期管理的垃圾回收

2 与std::unique_*ptr* 相比,std::shared_ptr的尺寸通常是裸指针尺寸的两倍,它还会带来控制块的开销,并要求原子化的引用计数操作

3 默认的资源析构通过delete运算符进行,但同时也支持定制删除器。删除器的型别对std::shared_ptr的型别没有影响

4 避免使用裸指针型别的变量来创建 std::shared_ptr 指针

1.4.3 std::weak_ptr:

1 使用std::weakptr 来代替可能空悬的std::shared_ptr

2 std::weak_ptr 可能的用武之地包括缓存,观察者列表,以及避免std::shared_ptr 指针环路。

看看大师的总结,完美!!

具体到完全代替裸指针,恐怕也要再掂量掂量。

下面写一些劣势

std::shared_ptr 会增加内存开销,复制的时候cpu消耗提高【原子count操作】

std::unique_ptr 内存占用小,几乎可以媲美裸指针;但是它毕竟是一个类,使用的时候,不能复制,导致你一个作用域内只能有一个可用的实例【类似rust的所有权吧,你用起来有点束手束脚】;

std::weak_ptr 必须跟std::shared_ptr配合使用。

优势:

省去你自己判断啥时候该释放资源【异步回调时候智能指针可以完美避免手动控制生命周期;enable_shared_frome_this 已经可以算是一种特别的编程技巧了

裸指针的操作习惯

1.对于性能和内存使用有严格要求的场景,不要过于依赖智能指针【比如嵌入式这些的,实际上C+class就够了】

2.对于类型不敏感的数据【也就是内存了】,可以考虑使用std::array或者std::vector等。因为这个时候,你实际上就是C的操作,类型对于该内存仅仅起到一个布局描述的作用,频繁的类型转换【非继承关系】、字段偏移等操作,用智能指针也没有什么好处【因为你还是会拿到裸指针去操作】

3.其他的对类型敏感,或者对作用域敏感的数据内存,可以都考虑使用智能指针。局部作用域使用uniqe_ptr , 多作用域的使用shared_ptr,缓存可能失效的情况下使用weak_ptr。

一般应用,除了容器,几乎一上来全部使用uniqe_ptr,当需要抛出一个副本的时候,使用shared_ptr。当功能完成的时候,哪个内存是全局生命周期,改成裸指针全局裸指针我都不判空】。如果该项目不是那么重要,甚至我都会全部用shared_ptr,不用关心性能问题,因为C++本身的编译性能已经很高了,随便写写性能就够了,只要不飞,内存泄漏不是问题。

当我要去判断某一个内存之后的操作会失效,但是不知道什么时候失效的时候,我使用weak_ptr和shared_ptr。通过weak_ptr接口可以线程安全的获取我之前的智能指针是否还生效。【这个时候,裸指针,几乎是没有办法的了,很容易出现野指针】

参考: https://blog.csdn.net/Darlingqiang/article/details/114065349?spm=1001.2014.3001.5501

https://zhuanlan.zhihu.com/p/29628938

https://www.zhihu.com/question/319277442/answer/1517987598

https://www.zhihu.com/question/319277442/answer/1517987598

https://blog.csdn.net/H_Strong/article/details/88754979?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161544736616780274137097%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161544736616780274137097&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-88754979.first_rank_v2_pc_rank_v29_10&utm_term=%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88

 类似资料: