首页 > 编程语言 >C++智能指针:weak_ptr实现详解

C++智能指针:weak_ptr实现详解

时间:2022-11-04 11:31:49浏览次数:74  
标签:std lock weak C++ shared ptr 指针


文章目录

  • ​​weak_ptr描述​​
  • ​​声明​​
  • ​​作用​​
  • ​​原理实现​​
  • ​​函数成员使用​​
  • ​​总结​​

weak_ptr描述

声明

头文件:​​<memory>​​​ 模版类:​​template <class T> class weak_ptr​​ 声明方式:​​std::weak_ptr<type_id> statement​

作用

根据boost库的官方描述,weak_ptr是由shared_ptr管理的一种弱引用对象的模版类。​​weak_ptr​​​的对象能够使用​​shared_ptr​​​指针的构造函数转换为一个shared_ptr对象。但是这里​​shared_ptr​​​的构造函数参数需要包含​​weak_ptr​​的lock成员,该成员是weak_ptr用来获取shared_ptr的指针。

这样当shared_ptr在多线程过程中被销毁时​​shared_ptr::reset​​​,weak_ptr的lock成员仍然能够保留shared_ptr的成员,直到当前shared_ptr正常终止,否则会出现非常危险的内存泄漏。关于lock()成员的作用如下描述:
如下代码

shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);

// some time later

if(int * r = q.get())
{
// use *r
}

多线程环境中shared_ptr是可以被多个线程共享,在r被使用之前p对象执行了​​p.reset()​​,正如我们上一篇文章中对shared_ptr的描述(​​C++智能指针:shared_ptr 实现详解​​),reset成员会重置当前shared_ptr指针的指向。此时,当前线程如果继续使用r指针,势必会产生访问空地址的异常问题

根据以上问题,使用​​weak_ptr::lock()​​成员来解决该问题

shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);

// some time later

//使用weak_ptr的lock成员来获取shared_ptr的指针
if(shared_ptr<int> r = q.lock())
{
// use *r
}

关于lock()成员简单说明一下,lock成员获取到的shared_ptr p指针创建一个临时对象(我们weak_ptr弱引用的体现),这个临时对象同样指向p,即使p执了reset这样的delete引用的操作,弱引用对象仍然持有改智能指针的地址,直到r指针的生命周期结束才会释放。不得不佩服C++语言设计者的脑洞,为了保持C++对内存操作的自由,即使耗费再大的精力也要实现这一目标,工匠精神才让今天的C++越来越被底层程序员喜欢。

原理实现

源码文件​​/boost/smart_ptr/weak_ptr.hpp​

template<class T> class weak_ptr
{
private:

// Borland 5.5.1 specific workarounds
typedef weak_ptr<T> this_type;
  • ​constructor​​ 构造函数
//默认构造函数
weak_ptr() BOOST_NOEXCEPT : px(0), pn() // never throws in 1.30+
{
}

//拷贝构造函数
weak_ptr( weak_ptr const & r ) BOOST_NOEXCEPT : px( r.px ), pn( r.pn )
{
}
  • ​destructor​​​析构函数,这里weak_ptr使用的是默认析构函数,一般使用​​expired​​返回空对象或者user_count()为0的情况则辅助shared_ptr释放引用
  • ​operator=​
1. 
weak_ptr & operator=( weak_ptr && r ) BOOST_NOEXCEPT
{
this_type( static_cast< weak_ptr && >( r ) ).swap( *this );
return *this;
}

//2.这里会更加安全,使用lock成员获取Y类型的指针
template<class Y>
weak_ptr & operator=( weak_ptr<Y> const & r ) BOOST_NOEXCEPT
{
boost::detail::sp_assert_convertible< Y, T >();

px = r.lock().get();
pn = r.pn;

return *this;
}

3.
template<class Y>
weak_ptr & operator=( shared_ptr<Y> const & r ) BOOST_NOEXCEPT
{
boost::detail::sp_assert_convertible< Y, T >();

px = r.px;
pn = r.pn;

return *this;
}
  • ​weak_ptr::swap​​成员,交换两个weak_ptr所指内容以及地址
void swap(this_type & other) BOOST_NOEXCEPT
{
//先交换地址,再交换内容
std::swap(px, other.px);
pn.swap(other.pn);
}
  • ​weak_ptr::reset​​成员,·重新指定对象地址和内容,就像是重新使用默认构造函数进行了初始化
void reset() BOOST_NOEXCEPT // never throws in 1.30+
{
//使用默认构造函数构造的对象和当前对象进行swap操作
this_type().swap(*this);
}
  • ​weak_ptr::use_count​​成员,获取shared_ptr对象被引用的次数。如果为空,则返回0
long use_count() const BOOST_NOEXCEPT
{
return pn.use_count();
}
  • ​weak_ptr::expired​​成员,当根据use_count==0来返回bool,其返回为true的时候,使用lock获取weak_ptr的指针只能获取到空指针
bool expired() const BOOST_NOEXCEPT
{
return pn.use_count() == 0;
}
  • ​weak_ptr::lock​​成员,会向weak_ptr对象返回一个shared_ptr。正如我们之前在weak_ptr作用中所描述的,防止多线程访问时的shared_ptr内存泄漏。此时weak_ptr对象获取到的指针为临时指针,会指向shared_ptr对象之前所指向的地址。
shared_ptr<T> lock() const BOOST_NOEXCEPT
{
return shared_ptr<T>( *this, boost::detail::sp_nothrow_tag() );
}

函数成员使用

  • 构造函数
#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
std::shared_ptr<int> sp (new int);

std::weak_ptr<int> wp1;
std::weak_ptr<int> wp2 (wp1);
std::weak_ptr<int> wp3 (sp);

std::cout << "use_count:\n";

//weak_ptr对象如果为经shared_ptr初始化,
//它是没有引用计数的,所以这里wp1和wp2引用计数都为0
//只有wp3经过了shared_ptr初始化,它的引用计数才为1

std::cout << "wp1: " << wp1.use_count() << '\n';
std::cout << "wp2: " << wp2.use_count() << '\n';
std::cout << "wp3: " << wp3.use_count() << '\n';

return 0;
}
  • 输出如下:
use_count:
wp1: 0
wp2: 0
wp3: 1
  • ​weak_ptr::operator=​​赋值运算符
// weak_ptr::operator= example
#include <iostream>
#include <memory>

int main () {
std::shared_ptr<int> sp1,sp2;
std::weak_ptr<int> wp;
// sharing group:
// --------------
sp1 = std::make_shared<int> (10); // sp1
wp = sp1; // sp1, wp

sp2 = wp.lock(); // sp1, wp, sp2
sp1.reset(); // wp, sp2

//通过lock保留的临时指针,重新获取到了shared_ptr共享的地址
sp1 = wp.lock(); // sp1, wp, sp2

std::cout << "*sp1: " << *sp1 << '\n';
std::cout << "*sp2: " << *sp2 << '\n';

return 0;
}
  • 输出如下
*sp1: 10
*sp2: 10
  • ​std::weak_ptr::swap​​交换指针地址以及对应的内容
#include <iostream>
#include <memory>

int main () {
std::shared_ptr<int> sp1 (new int(10));
std::shared_ptr<int> sp2 (new int(20));

std::weak_ptr<int> wp1(sp1);
std::weak_ptr<int> wp2(sp2);

std::cout << "wp1 -> " << *wp1.lock() << " " << wp1.lock() << '\n';
std::cout << "wp2 -> " << *wp2.lock() << " " << wp2.lock() << '\n';

//这里的swap仅仅是交换wp2的各自的指向地址
//并不会直接导致对应智能指针原始指针的地址交换
//根据输出,所以很明显,交换完成之后weak_ptr对象指向发生了变化,但是并未导致share_ptr指针的指向变化
wp1.swap(wp2);

std::cout << "sp1 -> " << *sp1 << " " << sp1 << '\n';
std::cout << "sp2 -> " << *sp2 << " " << sp2 << '\n';
std::cout << "wp1 -> " << *wp1.lock() << " " << wp1.lock() << '\n';
std::cout << "wp2 -> " << *wp2.lock() << " " << wp2.lock() << '\n';

return 0;
}
  • 输出如下:
wp1 -> 10 0x11daf90
wp2 -> 20 0x11dafd0
sp1 -> 10 0x11daf90
sp2 -> 20 0x11dafd0
wp1 -> 20 0x11dafd0
wp2 -> 10 0x11daf90
  • ​std::weak_ptr::reset​​成员,执行之后weak_ptr对象就像是重新执行了默认构造函数,又变成了一个空的对象
// weak_ptr::reset example
#include <iostream>
#include <memory>

int main () {
std::shared_ptr<int> sp (new int(10));

std::weak_ptr<int> wp(sp);

std::cout << "1. wp " << (wp.expired()?"is":"is not") << " expired\n";
std::cout << "4. wp " << wp.use_count() << " *wp " << *wp.lock() << '\n';

wp.reset();

std::cout << "2. wp " << (wp.expired()?"is":"is not") << " expired\n";
std::cout << "3. sp " << sp.use_count() << " *sp " << *sp << '\n';

return 0;
}
  • 输出如下
1. wp is not expired
2. wp 2 *wp 10
3. wp is expired
4. sp 1 *sp 10

总结

shared_ptr和weak_ptr主要区别如下

  1. shared_ptr对象能够初始化实际指向一个地址内容而weak_ptr对象没办法直接初始化一个具体地址,它的对象需要由shared_ptr去初始化
  2. weak_ptr不会影响shared_ptr的引用计数,因为它是一个弱引用,只是一个临时引用指向shared_ptr。即使用shared_ptr对象初始化weak_ptr不会导致shared_ptr引用计数增加。依此特性可以解决shared_ptr的循环引用问题。
  3. weak_ptr没有解引用*和获取指针->运算符,它只能通过lock成员函数去获取对应的shared_ptr智能指针对象,从而获取对应的地址和内容。

参考文档:
​​​http://www.cplusplus.com/reference/memory/weak_ptr/​​​​https://www.boost.org/doc/libs/1_66_0/libs/smart_ptr/doc/html/smart_ptr.html#weak_ptr​


标签:std,lock,weak,C++,shared,ptr,指针
From: https://blog.51cto.com/u_13456560/5823181

相关文章

  • C++设计一个类:不能被继承
    C++如何设计一个不能被继承的类?我们首先想到,不能被继承,那把构造函数和析构函数设计成私有的不就行了,这样的话子类不能访问父类的构造函数和析构函数,也就无法继承了。然而这......
  • C++——单调队列
    classSolution{public:classMyqueue//单调队列{public:deque<int>que;//因为只维护了队列最大值,故在pop时判断滑动窗口最......
  • C++ 获取时间戳
    获取unix时间戳std::time_tresult=std::time(nullptr);或者:constautop1=std::chrono::system_clock::now();std::cout<<"secondssinceepoch:......
  • c++11 unique_ptr
    unique_ptr使用详解      ......
  • C++静态成员和静态函数的正例和反例
      上图所示的代码都是正确的、并且能够按正常人的预期执行。首先提示一点、C++要用类名调用静态函数或者引用变量时、不是像Java一样用点号、而是用两个冒号! 错误......
  • C++中const修饰的成员函数
    const成员函数的格式:只要在函数后加上一个const就可以了TypeClassName::function(Typep)constintgetFoot(void)const{returnthis->foot; }https://blog.5......
  • C++17 The Complete Guide 电子书 pdf
    作者:[德]NicolaiM·Josuttis 链接:C++17TheCompleteGuide ......
  • C++——指针
    指针基本概念C++的指针也是标识符,不能与其它的普通变量重名;对指针的赋值操作通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”。指针占用的内存指针也......
  • C++Builder(BCB)学习群(QQ)
    点击链接加入群聊【C++Builder(BCB)学习群】:https://jq.qq.com/?_wv=1027&k=FHyDxiRw群名称:C++Builder(BCB)学习群群号:646968133......
  • C++ 通用的 toString() 函数
    1#include<iostream>2#include<string>34namespacestr_utils{56std::stringto_string(constchar*c_str){7std::cout<<"调用了......