前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)

从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)

作者头像
s1mba
发布于 2022-01-05 05:59:39
发布于 2022-01-05 05:59:39
1.4K00
代码可运行
举报
文章被收录于专栏:开发与安全开发与安全
运行总次数:0
代码可运行

一、boost 智能指针

智能指针是利用RAII(Resource Acquisition Is Initialization:资源获取即初始化)来管理资源。关于RAII的讨论可以参考前面的

。在使用boost库之前应该先下载后放在某个路径,并在VS 包含目录中添加。下面是boost 库里面的智能指针:

(一)、scoped_ptr<T>

先来看例程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <boost/scoped_ptr.hpp>
#include <iostream>
using  namespace std;

class X
{
public:
    X()
    {
        cout <<  "X ..." << endl;
    }
    ~X()
    {
        cout <<  "~X ..." << endl;
    }
};

int main( void)
{
    cout <<  "Entering main ..." << endl;
    {
        boost::scoped_ptr<X> pp( new X);

         //boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移
    }
    cout <<  "Exiting main ..." << endl;

     return  0;
}

来稍微看一下scoped_ptr 的简单定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace boost
{

     template< typename T>  class scoped_ptr : noncopyable
    {
     private:

        T *px;

        scoped_ptr(scoped_ptr  const &);
        scoped_ptr & operator=(scoped_ptr  const &);

         typedef scoped_ptr<T> this_type;

         void  operator==( scoped_ptr  const & )  const;
         void  operator!=( scoped_ptr  const & )  const;
     public:
         explicit scoped_ptr(T *p =  0);
        ~scoped_ptr();

         explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
         void reset(T *p =  0);

        T & operator*()  const;
        T * operator->()  const;
        T *get()  const;

         void swap(scoped_ptr &b);
    };

     template< typename T>
     void swap(scoped_ptr<T> &a, scoped_ptr<T> &b);
}

auto_ptr类似,内部也有一个T* px; 成员 ,智能指针对象pp 生存期到了,调用析构函数,在析构函数内会delete px; 如下面所说:

scoped_ptr mimics a built-in pointer except that it guarantees deletion of the object pointed to, either on destruction of the scoped_ptr or via an

explicit reset(). scoped_ptr is a simple solution for simple needs; use shared_ptr or std::auto_ptr if your needs are more complex.

从上面的话可以得知当调用reset() 函数时也能够释放堆对象,如何实现的呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void reset(T *p =  0)   // never throws
{
    BOOST_ASSERT( p ==  0 || p != px );  // catch self-reset errors
    this_type(p).swap(* this);
}
void swap(scoped_ptr &b)   // never throws
{
    T *tmp = b.px;
    b.px = px;
    px = tmp;
}

typedef scoped_ptr<T> this_type; 当调用pp.reset(),reset 函数构造一个临时对象,它的成员px=0, 在swap 函数中调换 pp.px 与

(this_type)(p).px, 即现在pp.px = 0; //解绑

临时对象接管了裸指针(即所有权可以交换),reset 函数返回,栈上的临时对象析构,调用析构函数,进而delete px;

另外拷贝构造函数和operator= 都声明为私有,故所有权不能转移,且因为容器的push_back 函数需要调用拷贝构造函数,故也不能

将scoped_ptr 放进vector,这点与auto_ptr 相同(不能共享所有权)。此外,还可以使用 auto_ptr 对象 构造一个scoped_ptr 对象:

scoped_ptr( std::auto_ptr<T> p ): px( p.release() );

由于scoped_ptr是通过delete来删除所管理对象的,而数组对象必须通过deletep[]来删除,因此boost::scoped_ptr是不能管理数组对象的,如果

要管理数组对象需要使用boost::scoped_array类。

boost::scoped_ptr和std::auto_ptr的功能和操作都非常类似,如何在他们之间选取取决于是否需要转移所管理的对象的所有权(如是否需要作为

函数的返回值)。如果没有这个需要的话,大可以使用boost::scoped_ptr,让编译器来进行更严格的检查,来发现一些不正确的赋值操作。

(二)、shared_ptr<T>

An enhanced relative of scoped_ptr with reference counted copy semantics. The object pointed to is deleted when the last

shared_ptr pointing to it is destroyed or reset.

先来看例程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <boost/shared_ptr.hpp>
#include <iostream>
using  namespace std;

class X
{
public:
    X()
    {
        cout <<  "X ..." << endl;
    }
    ~X()
    {
        cout <<  "~X ..." << endl;
    }
};

int main( void)
{
    cout <<  "Entering main ..." << endl;
    boost::shared_ptr<X> p1( new X);
    cout << p1.use_count() << endl;
    boost::shared_ptr<X> p2 = p1;
     //boost::shared_ptr<X> p3;
     //p3 = p1;

    cout << p2.use_count() << endl;
    p1.reset();
    cout << p2.use_count() << endl;
    p2.reset();
    cout <<  "Exiting main ..." << endl;
     return  0;
}

图示上述程序的过程也就是:

再深入一点,看源码,shared_ptr 的实现 比 scoped_ptr 要复杂许多,涉及到多个类,下面就不贴完整源码,看下面的类图:

执行 boost::shared_ptr<X> p1(new X); 这一行之后:

而执行 p1.use_count(); 先是 pn.use_count(); 接着 pi_ != 0? pi_->use_count(): 0; return use_count_; 即返回1.

接着执行 boost::shared_ptr<X> p2 = p1;

本想跟踪shared_ptr 的拷贝构造函数,在当行设置断点后F11直接跳过了,说明是shared_ptr类没有实现拷贝构造函数,使用的是编译器默认的拷

贝构造函数,那如何跟踪呢?如果你的C++基础比较好,可以想到拷贝构造函数跟构造函数一样,如果有对象成员是需要先构造对象成员的(这一点

也可以从调用堆栈上看出),故可以在shared_count 类的拷贝构造函数设置断点,然后就可以跟踪进去,如下的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// shared_count
shared_count(shared_count  const &r): pi_(r.pi_)   // nothrow
{
     if( pi_ !=  0 ) pi_->add_ref_copy();
}
// sp_counted_base
void add_ref_copy()
{
    BOOST_INTERLOCKED_INCREMENT( &use_count_ );
}

故p2.pn.pi_ 也指向 唯一的一个 sp_counted_impl_p 对象,且use_count_ 增1.

再者,shared_ptr 类的默认拷贝构造函数是浅拷贝,故现在p2.px 也指向 X.

由于p2 和 p1 共享一个sp_counted_impl_p 对象,所以此时无论打印p2.use_count(); 还是 p1.use_count(); 都是2。

接着执行p1.reset();

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// shared_ptr
void reset()  // never throws in 1.30+
{
    this_type().swap(* this);
}
void swap(shared_ptr<T> &other)   // never throws
{
    std::swap(px, other.px);
    pn.swap(other.pn);
}

this_type() 构造一个临时对象,px = 0, pn.pi_ = 0; 然后swap交换p1 与 临时对象的成员,即现在p1.px = 0; p1.pn.p1_ = 0; 如上图。

reset 函数返回,临时对象需要析构,但跟踪时却发现直接返回了,原因跟上面的一样,因为shared_ptr 没有实现析构函数,调用的是默认的析构函

数,与上面拷贝函数同样的道理,可以在shared_count 类析构函数设置断点,因为pn 是对象成员,故析构函数也会被调用。如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//shared_count
~shared_count()  // nothrow
{
     if( pi_ !=  0 ) pi_->release();
}

// sp_counted_base
void release()  // nothrow
{
     if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) ==  0 )
    {
        dispose();
        weak_release();
    }
}

现在use_count_ 减为1,但还不为0,故 dispose(); 和 weak_release(); 两个函数没有被调用。当然此时打印 p2.use_count() 就为1 了。

最后 p2.reset(); 跟p1.reset(); 同样的流程,只不过现在执行到release 时,use_count_ 减1 为0;需要继续执行dispose(); 和

weak_release(); 如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

//sp_counted_impl_p
virtual  void dispose()  // nothrow
{
    boost::checked_delete( px_ );
}
//sp_counted_base
void weak_release()  // nothrow
{
     if( BOOST_INTERLOCKED_DECREMENT( &weak_count_ ) ==  0 )
    {
        destroy();
    }
}

virtual  void destroy()  // nothrow
{
     delete  this;
}

在check_delete 中会 delete px_; 也就是析构 X。接着因为weak_count_ 减1 为0, 故执行destroy(); 函数里面delete this; 即析构自身

(sp_counted_impl_p 对象是在堆上分配的)。

说到这里,我们也可以明白,即使最后没有调用p2.reset(); 当p2 栈上对象生存期到, 需要调用shared_ptr 类析构函数,进而调用shared_count 类析

构函数,所以执行的结果也是跟reset() 一样的,只不过少了临时对象this_type()的构造。

总结一下:

和前面介绍的boost::scoped_ptr相比,boost::shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限制(还是有一些需要遵循的

使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。

boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::shared_ptr:

  1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
  2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。
  3. 不要构造一个临时的shared_ptr作为函数的参数。

详见 http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm

如下列bad 函数内 的代码则可能导致内存泄漏:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void f(shared_ptr< int>,  int);
int g();

void ok()
{
    shared_ptr< int> p( new  int( 2));
    f(p, g());
}

void bad()
{
    f(shared_ptr< int>( new  int( 2)), g());
}

如bad 函数内,假设先构造了堆对象,接着执行g(), 在g 函数内抛出了异常,那么由于裸指针还没有被智能指针接管,就会出现内存泄漏。

(三)、weak_ptr<T>

如上总结shared_ptr<T> 时说 到引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <boost/shared_ptr.hpp>
#include <iostream>
using  namespace std;

class Parent;
class Child;
typedef boost::shared_ptr<Parent> parent_ptr;
typedef boost::shared_ptr<Child> child_ptr;

class Child
{
public:
    Child()
    {
        cout <<  "Child ..." << endl;
    }
    ~Child()
    {
        cout <<  "~Child ..." << endl;
    }
    parent_ptr parent_;
};

class Parent
{
public:
    Parent()
    {
        cout <<  "Parent ..." << endl;
    }
    ~Parent()
    {
        cout <<  "~Parent ..." << endl;
    }
    child_ptr child_;
};

int main( void)
{
    parent_ptr parent( new Parent);
    child_ptr child( new Child);
    parent->child_ = child;
    child->parent_ = parent;

     return  0;
}

如上述程序的例子,运行程序可以发现Child 和 Parent 构造函数各被调用一次,但析构函数都没有被调用。 由于Parent和Child对象互相引用,

它们的引用计数最后都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了 内存泄漏 。

其中一种解决循环引用问题的办法是 手动打破循环引用,如在return 0; 之前加上一句 parent->child_.reset(); 此时

当栈上智能指针对象child 析构,Child 对象引用计数为0,析构Chlid 对象,它的成员parent_ 被析构,则Parent 对象引用计数

减为1,故当栈上智 能指针对象parent 析构时,Parent 对象引用计数为0,被析构。

但手动释放不仅麻烦而且容易出错,这里主要介绍一下弱引用智能指针 weak_ptr<T> 的用法,下面是简单的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace boost
{

     template< typename T>  class weak_ptr
    {
     public:
         template < typename Y>
        weak_ptr( const shared_ptr<Y> &r);

        weak_ptr( const weak_ptr &r);

         template< class Y>
        weak_ptr & operator=( weak_ptr<Y> && r );

         template< class Y>
        weak_ptr & operator=(shared_ptr<Y>  const &r);


        ~weak_ptr();

         bool expired()  const;
        shared_ptr<T> lock()  const;
    };
}

上面出现了 && 的用法,在这里并不是逻辑与的意思,而是C++ 11中的新语法,如下解释:

&& is new in C++11, and it signifies that the function accepts an RValue-Reference -- that is, a reference to an argument that is about

to be destroyed.

两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用智能指针。

强引用与弱引用

强引用,只要有一个引用存在,对象就不能释放

弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在

通过weak_ptr访问对象的成员的时候,要提升为shared_ptr

如果存在,提升为shared_ptr(强引用)成功 如果不存在,提升失败

对于上述的例子,只需要将Parent 类里面的成员定义改为如下,即可解决循环引用问题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Parent
{
public:
    boost::weak_ptr<parent> child_;
};

因为此例子涉及到循环引用,而且是类成员引用着另一个类,涉及到两种智能指针,跟踪起来难度很大,我也没什么心情像分析

shared_ptr 一样画多个图来解释流程,这个例子需要解释的代码远远比shared_ptr 多,这里只是解释怎样使用,有兴趣的朋友自

己去分析一下。

下面再举个例子说明lock() 和 expired() 函数的用法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
using  namespace std;

class X
{
public:
    X()
    {
        cout <<  "X ..." << endl;
    }
    ~X()
    {
        cout <<  "~X ..." << endl;
    }

     void Fun()
    {
        cout <<  "Fun ..." << endl;
    }
};
int main( void)
{
    boost::weak_ptr<X> p;
    boost::shared_ptr<X> p3;
    {
        boost::shared_ptr<X> p2( new X);
        cout << p2.use_count() << endl;
        p = p2;
        cout << p2.use_count() << endl;

         /*boost::shared_ptr<X> */
        p3 = p.lock();
        cout << p3.use_count() << endl;
         if (!p3)
            cout <<  "object is destroyed" << endl;
         else
            p3->Fun();
    }
     /*boost::shared_ptr<X> p4 = p.lock();
    if (!p4)
        cout<<"object is destroyed"<<endl;
    else
        p4->Fun();*/

     if (p.expired())
        cout <<  "object is destroyed" << endl;
     else
        cout <<  "object is alived" << endl;

     return  0;
}

从输出可以看出,当p = p2; 时并未增加use_count_,所以p2.use_count() 还是返回1,而从p 提升为 p3,增加了

use_count_, p3.use_count() 返回2;出了大括号,p2 被析构,use_count_ 减为1,程序末尾结束,p3 被析构,

use_count_ 减为0,X 就被析构了。

参考 :

C++ primer 第四版 Effective C++ 3rd C++编程规范

http://www.cnblogs.com/TianFang/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2013/07/29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】智能指针
下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到执⾏,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了。
用户11290673
2025/02/14
480
【C++】智能指针
初识C++ · 智能指针
智能指针的引入,我们得先从异常开始说起,异常面临的一个窘境是new了多个对象,抛异常了会导致先new的对象没有析构,从而导致内存泄漏的问题,解决方法是使用RAII。
_lazy
2024/10/16
960
初识C++ · 智能指针
【C++】智能指针的使用及其原理
下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到 执⾏,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛 出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起 来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了。
用户11375356
2025/02/14
1590
【C++】智能指针的使用及其原理
C++智能指针「建议收藏」
以上代码运行时,由于ptr2拷贝构造时默认是浅拷贝,两个对象底层的裸指针指向同一份资源,对象析构时,会出现同一资源释放两次的错误(释放野指针),这里需要解决两个问题:
全栈程序员站长
2022/08/31
5250
C++智能指针「建议收藏」
现代C++之手写智能指针
这个类可以完成智能指针的最基本的功能:对超出作用域的对象进行释放。但它缺了点东 西:
公众号guangcity
2019/12/30
3K0
现代C++之手写智能指针
C++:智能指针
在学习异常的时候,我们知道了由于异常的反复横跳可能会导致内存泄露的问题,但是对于一些自定类类型来说他在栈帧销毁的时候会去调用对应的析构函数,但是以下这种必须手动释放的场景,一旦抛出异常就会造成内存泄露的结果。
小陈在拼命
2024/05/27
1300
C++:智能指针
智能指针探究
那么为了解决浅拷贝的问题,我们用不带引用计数的智能指针和带引用计数的智能指针来解决
无敌清风蓝
2024/06/04
1140
智能指针探究
C++ 智能指针详解
http://blog.csdn.net/xt_xiaotian/article/details/5714477
bear_fish
2018/09/20
2K0
Boost C++ 库 | 智能指针(共享指针、共享数组、弱指针、介入式指针、指针容器)入门
Qt历险记
2024/10/10
2450
Boost C++ 库 | 智能指针(共享指针、共享数组、弱指针、介入式指针、指针容器)入门
【c++】智能指针详解&&c++特殊类设计&&c++的类型转换
下面我们先分析一下下面这段程序有没有什么内存方面的问题?提示一下:注意分析MergeSort函数中的问题
用户10925563
2024/08/06
2190
【c++】智能指针详解&&c++特殊类设计&&c++的类型转换
【C++】智能指针 && 守卫锁
​ 上述代码中,main 函数中捕获 Func 的异常,而非常关键,因为可能会导致一些内存泄漏的问题,new 会去调用 operator[],而这是有可能会申请失败的,一旦失败就会抛异常被 main 函数捕捉到,那么 Func 下面的 delete 就没有被执行到,这不是妥妥的内存泄漏吗❓❓❓
利刃大大
2025/03/03
700
【C++】智能指针 && 守卫锁
详解 C++ 11 中的智能指针
C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒,手动分配内存与手动释放内存有利也有弊,自动分配内存和自动释放内存亦如此,这是两种不同的设计哲学。有人认为,内存如此重要的东西怎么能放心交给用户去管理呢?而另外一些人则认为,内存如此重要的东西怎么能放心交给系统去管理呢?在 C/C++ 语言中,内存泄露的问题一直困扰着广大的开发者,因此各类库和工具的一直在努力尝试各种方法去检测和避免内存泄露,如 boost,智能指针技术应运而生。
范蠡
2019/10/25
2.8K0
详解 C++ 11 中的智能指针
C++智能指针详解
智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏和空悬指针等等问题。
小灵蛇
2024/06/06
1490
C++智能指针详解
使用 C++ 智能指针遇到的坑
C++11 中推出了三种智能指针,unique_ptr、shared_ptr 和 weak_ptr,同时也将 auto_ptr 置为废弃 (deprecated)。
早起的鸟儿有虫吃
2021/08/13
2.8K0
C++智能指针
C++中,动态内存的管理是通过一对运算符来完成的,new用于申请内存空间,调用对象构造函数初始化对象并返回指向该对象的指针。delete接收一个动态对象的指针,调用对象的析构函数销毁对象,释放与之关联的内存空间。动态内存的管理在实际操作中并非易事,因为确保在正确的时间释放内存是极其困难的,有时往往会忘记释放内存而产生内存泄露;有时在上游指针引用内存的情况下释放了内存,就会产生非法的野指针(悬挂指针)。
恋喵大鲤鱼
2018/08/03
3.6K0
C++智能指针
【C++】智能指针
若p2处new抛异常,则相当于p2的new没有成功,而p1的new成功了,所以需要释放p1,然后再重新抛出
lovevivi
2023/10/17
1860
【C++】智能指针
初探C++11智能指针
在远古时代,C++发明了指针这把双刃剑,既可以让程序员精确地控制堆上每一块内存,也让程序更容易发生crash,大大增加了使用指针的技术门槛。因此,从C++98开始便推出了auto_ptr,对裸指针进行封装,让程序员无需手动释放指针指向的内存区域,在auto_ptr生命周期结束时自动释放,然而,由于auto_ptr在转移指针所有权后会产生野指针,导致程序运行时crash,如下面示例代码所示:
forrestlin
2019/07/23
1.3K0
C++智能指针
func函数中在堆中申请了资源,在func函数结束前也要释放资源,又因为异常的原因,所以抛异常的前面还需要再加一次资源释放,这非常的不方便,代码看起来也很差劲。 并且new本身也会抛异常,如果都抛异常了怎么办,也很容易导致内存泄漏。 所以这里就有了智能指针,这是大体思路:
有礼貌的灰绅士
2023/06/14
1900
C++智能指针
C++智能指针
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
二肥是只大懒蓝猫
2023/03/30
7520
C++智能指针
C++智能指针
C++智能指针 零、前言 一、为什么需要智能指针 二、内存泄漏 三、智能指针 1、RAII 2、智能指针的原理 3、std::auto_ptr 4、std::unique_ptr 5、std::shared_ptr 6、std::weak_ptr 7、删除器 8、C++11和boost中智能指针的关系 零、前言 本章主要讲解学习C++中智能指针的概念及使用 一、为什么需要智能指针 示例: double Division(int a, int b) { // 当b == 0时抛出异常 if (b =
用户9645905
2022/11/15
6430
C++智能指针
相关推荐
【C++】智能指针
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验