智能指针是 C++ 中用于管理动态分配的对象生命周期的一种特殊指针。它们提供了自动内存管理和资源释放的机制,避免了手动调用 delete 来释放内存的麻烦和潜在的内存泄漏。

C++ 标准库提供了两种常用的智能指针: std::shared_ptrstd::unique_ptr
(其实还有 std::weak_ptr ,用于破解循环引用的问题)

  1. std::shared_ptr :它是一种共享所有权的智能指针。多个 std::shared_ptr 对象可以同时拥有同一个对象的所有权。它使用引用计数的方式来跟踪对象的引用次数。当最后一个 std::shared_ptr 对象超出作用域或被显式地设置为 nullptr ,引用计数变为零时,对象会自动销毁。这样可以确保对象在没有引用时被正确地释放,避免了内存泄漏。
  2. std::unique_ptr :它是一种独占所有权的智能指针。一个 std::unique_ptr 对象拥有唯一的所有权,不能被其他智能指针共享。它使用了移动语义,因此具有很高的效率。当 std::unique_ptr 对象超出作用域时,或者通过 std::move 转移所有权时,对象会自动销毁。这样可以避免资源的重复释放和悬空指针的问题。

智能指针的好处在于它们提供了自动资源管理,减少了手动管理内存的错误和复杂性。此外,它们还能帮助解决资源泄漏、悬空指针和内存安全等问题。

以下是使用智能指针的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
#include <memory>

int main() {
// 使用 std::shared_ptr
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::shared_ptr<int> anotherSharedPtr = sharedPtr;

// 使用 std::unique_ptr
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);

return 0;
}

在这个示例中, std::shared_ptrstd::unique_ptr 能够自动管理动态分配的整数对象的生命周期。无需手动释放内存,当 std::shared_ptr 对象超出作用域时,引用计数减少;当 std::unique_ptr 对象超出作用域时,对象自动销毁。

# 底层实现

当我们创建智能指针 shared_ptr 指向一个对象的时候,其构造函数会分配一个空间创建一个管理对象 manager object ,这个管理对象是动态分配的,其包含了一个裸指针,指向我们要管理的对象,还包含了 shared_ptr 的引用计数和 weak_ptr 的引用计数
一开始,当创建一个 shared_ptr 指向一个新创建的对象,显然,sp 的引用计数为 1,wp 的引用计数为 0,然后裸指针指向对象。
正如我们所经常听说的那样,当 sp 引用计数为 0 时,对象会销毁。这里多了一个不为大家所熟知的小细节,就是此时这个管理对象还不会释放,只有等 wp 也减为 0 的时候,才会释放这个管理对象

# 关于智能指针的注意事项

在使用智能指针时,有一些注意事项需要注意:

  1. 避免循环引用:如果使用 std::shared_ptr ,应避免形成循环引用,即两个或多个对象之间相互持有 std::shared_ptr ,导致引用计数无法降为零,从而导致内存泄漏。可以通过使用 std::weak_ptr 打破循环引用来解决这个问题。
  2. 不要使用裸指针和智能指针混合使用:尽量避免在代码中同时使用裸指针和智能指针,因为这可能会导致资源管理的混乱。如果需要使用裸指针,可以使用 std::unique_ptr::get() 方法获取裸指针。
  3. 使用合适的智能指针类型:根据情况选择合适的智能指针类型,例如使用 std::shared_ptr 进行共享所有权的管理,或者使用 std::unique_ptr 进行独占所有权的管理。选择适当的智能指针类型可以提高代码的可读性和安全性。
  4. 只能用智能指针指向用 new 分配的、能够用 delete 释放的对象上
  5. 尽可能地用 make_shared 去代替 new(但它也不是百利而无一害,见下文:make_shared

# 智能指针作为函数的入口参数和返回参数

智能指针可以作为函数的参数和返回值类型,从而方便地管理动态分配的资源。

以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <memory>
#include <iostream>

// 函数参数为智能指针类型
void processObject(std::shared_ptr<int> ptr) {
// 使用智能指针操作对象
std::cout << "Object value: " << *ptr << std::endl;
}

// 函数返回智能指针类型
std::unique_ptr<int> createObject() {
// 创建动态分配的对象,并返回智能指针
return std::make_unique<int>(42);
}

int main() {
// 使用智能指针作为函数参数
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
processObject(sharedPtr);

// 使用智能指针作为函数返回值
std::unique_ptr<int> uniquePtr = createObject();
std::cout << "Object value: " << *uniquePtr << std::endl;

return 0;
}

在这个示例中, processObject 函数接受一个 std::shared_ptr<int> 参数,用于处理动态分配的整数对象。 createObject 函数创建一个动态分配的整数对象,并返回一个 std::unique_ptr<int>

最后,再回答 make_shared 的作用和对象存放位置的问题。 std::make_shared 是一个模板函数,用于创建智能指针对象,它的作用是在一次内存分配中同时创建对象和管理对象的引用计数。由于对象和引用计数信息存储在一块连续的内存中,因此可以提高性能和内存利用率。

至于对象存放的位置, std::make_shared 创建的对象和引用计数信息通常存储在一块连续的内存块中,这个内存块位于堆上。这样的设计可以减少内存碎片化,并提高内存访问的效率。

# weak_ptr

1、初始化:
wp 的初始化默认是空的,不指向任何东西 —— 甚至不指向一个 manager object。
所以你只能通过从 shared_ptr 或现有的 weak_ptr 进行拷贝构造或复制构造然后将 weak_ptr 指向它:

1
2
3
4
5
6
7
shared_ptr<Thing> sp(new Thing);
weak_ptr<Thing> wp1(sp); // construct wp1 from a shared_ptr
weak_ptr<Thing> wp2; // an empty weak_ptr - points to nothing
wp2 = sp; // wp2 now points to the new Thing
weak_ptr<Thing> wp3(wp2); // construct wp3 from a weak_ptr
weak_ptr<Thing> wp4
wp4 = wp2; // wp4 now points to the new Thing.

2、引用
你不能直接用 wp 去引用对象,而是通过一个 sp 来接住 wp.lock() 返回的内容。lock 函数会判断当前 wp 指向的对象是否还存在 / 有效,如果存在,该函数会返回一个该对象的 sp,如果不存在,则返回一个空的 sp:

1
shared_ptr<Thing> sp2 = wp2.lock(); // get shared_ptr from weak_ptr

通常,我们有 3 种方式去引用一个 wp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//第一种方法,通过 sp 和 lock,然后判断 sp 是否为空
void do_it(weak_ptr<Thing> wp){
shared_ptr<Thing> sp = wp.lock(); // get shared_ptr from weak_ptr
if(sp) {
sp->defrangulate(); // tell the Thing to do something
} else {
cout << "The Thing is gone!" << endl;
}
}

//第二种方法,利用 wp 的 expired 函数
bool is_it_there(weak_ptr<Thing> wp) {
if(wp.expired()) {
cout << "The Thing is gone!" << endl;
return false;
}
return true;
}

//第三种方式,利用 wp 构造一个 sp,然后捕获异常
void do_it(weak_ptr<Thing> wp){
shared_ptr<Thing> sp(wp); // construct shared_ptr from weak_ptr
// exception thrown if wp is expired, so if here, sp is good to go
sp->defrangulate(); // tell the Thing to do something
}
...

try {
do_it(wpx);
} catch(bad_weak_ptr&) {
cout << "A Thing (or something else) has disappeared!" << endl;
}

3、特殊使用场景
wp 除了下文提到的解决循环引用的问题之外,还有一个特殊的使用场景就是获取对象的 this 指针。
当一个对象要通过 shared_ptr 的形式获取一个 this 指针以便实现自动的内存管理时,如果我们仅仅通过拷贝 this 指针来构造 sp 的话,由于 this 指针是个裸指针,于是又会生成一个新的管理对象来指向被管理的对象,于是现在有两个 manager object 了。那么最终就很可能造成内存重复释放的错误:

1
2
3
4
5
6
7
void Thing::foo()
{
// we need to transmogrify this object
shared_ptr<Thing> sp_for_this(this); // danger! a second manager object!
transmogrify(sp_for_this);
}
//假设 Thing 对象本身就是用 sp 创建的,那么当其调用 foo 函数时,又将创建一个管理对象

为了解决这个问题,需要借助 std::enabled_shared_from_this 来实现,是一个类模板,将 Thing 继承自它,然后再需要用到 this 指针的地方通过调用 shared_from_this() 这个函数来实现。

enabled_shared_from_this 的底层逻辑大概就是在定义类时,类还有一个 wp 的成员变量指向负责管理该类对象的 manager object,然后每次我们调用 shared_from_this 的时候就是拿到 wp 所指向的这个 manager object 所指向的对象 —— 不会多生成一个管理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Thing : public enable_shared_from_this<Thing> {
public:
void foo();
void defrangulate();
};

void Thing::foo() {
// we need to transmogrify this object
// get a shared_ptr from the weak_ptr in this object
shared_ptr<Thing> sp_this = shared_from_this();
transmogrify(sp_this);
}

void transmogrify(shared_ptr<Thing> ptr) {
ptr->defrangulate();
/* etc. */
}

int main() {
// The following starts a manager object for the Thing and also
// initializes the weak_ptr member that is now part of the Thing.
shared_ptr<Thing> t1(new Thing);
t1->foo();
...
}

# make_shared

当我们创建一个 sp 指向一个新 new 出来的对象时,一种语法是:

1
shared_ptr<Thing> p(new Thing);

在这种情况下,会触发两次内存分配,一次是调用 Thing 的构造函数,分配其内存,还有一次是调用 sp 的构造函数,分配其内存给一个管理对象。而两次内存分配会导致性能 / 效率的降低。

所以,为了优化这个问题,有了 make_shared 这个函数,用来一次分配足够的内存:

1
shared_ptr<Thing> p(make_shared<Thing>()); // only one allocation!

在这种情况下,只会发生一次内存分配,效率提高。

这里有个小问题,为什么 make_shared 也需要传入一个模板参数 Thing 呢?是为了灵活性考虑,这样的话 make_shared 生成的对象就可以是其他类型然后 cast 造型给 sp 使用。比如在继承关系中用于父类、子类的指针指向:

1
shared_ptr<Base> bp(make_shared<Derived1>());

最后,如果 Thing 的构造函数需要传递参数,在 make_shared 后面接着加上即可:

1
shared_ptr<Thing> p (make_shared<Thing>(42, "I'm a Thing!"));

这里提一嘴,虽然说我们用 make_shared 可以优化效率,但是它也不是就完全没有缺点。根据前面 “底层实现” 提到的内容,如果我们用 make_shared 生成的内容,sp 已经减为 0 了,但是 wp 还大于 0,那么整块内存都不会被释放。

# 循环引用

循环引用是指两个或多个对象相互持有 std::shared_ptr ,导致它们之间的引用计数无法降为零,从而造成内存泄漏。当存在循环引用时,即使不再使用这些对象,它们所占用的内存也无法被释放,从而导致资源泄漏。

下面我们通过一个简单的示例来展示循环引用问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <memory>

class B; // 前向声明

class A {
public:
std::shared_ptr<B> bPtr;

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

class B {
public:
std::shared_ptr<A> aPtr;

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

int main() {
std::shared_ptr<A> aPtr = std::make_shared<A>();
std::shared_ptr<B> bPtr = std::make_shared<B>();

// 建立循环引用
aPtr->bPtr = bPtr;
bPtr->aPtr = aPtr;

return 0;
}

在上述示例中,我们定义了两个类 AB ,它们分别包含一个 std::shared_ptr 成员变量用于相互引用。在 main 函数中,我们创建了两个对象 aPtrbPtr ,然后通过相互赋值建立了循环引用。

当程序结束时,这两个对象的析构函数并不会被调用,因为它们之间的循环引用导致引用计数无法降为零。这就造成了内存泄漏,因为这些对象所占用的内存无法被释放。

为了解决循环引用问题,C++ 提供了 std::weak_ptrstd::weak_ptr 是一种弱引用,它可以引用 std::shared_ptr 指向的对象,但并不会增加对象的引用计数。通过将其中一个指针使用 std::weak_ptr 来打破循环引用,从而实现对象的正确释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//于是,只需要把上面的其中一个指针声明为weak_ptr即可
#include <iostream>
#include <memory>

class B;  //Forward declaration
class A {
public:
    std::shared_ptr<B> bPtr;
    ~A() {
        std::cout << "A destructor called" << std::endl;
    }
};

class B {
public:
    // std::shared_ptr<A> aPtr;
    std::weak_ptr<A> aPtr;  //use weak ptr to break the loop

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

int main() {
    while(1) {
        std::shared_ptr<A> aPtr = std::make_shared<A>();
        std::shared_ptr<B> bPtr = std::make_shared<B>();
        // make it loop
        aPtr->bPtr = bPtr;
        bPtr->aPtr = aPtr;
        std::cout << "in while loop..." << std::endl;
    } return 0;
}

std::weak_ptr 并不能直接打破循环引用,而是通过辅助的机制来解决循环引用的问题。它并不增加引用计数,因此即使存在循环引用,对象的引用计数也会降为零,从而触发析构函数的调用。

在前面的示例中,当 aPtrbPtr 的引用计数降为零时,它们的析构函数会被调用,从而释放它们所占用的内存。由于 bPtr 使用的是 std::weak_ptr ,它并不增加 aPtr 的引用计数,因此循环引用并不会阻止 aPtr 的析构。这样就避免了 std::shared_ptr 循环引用导致的内存泄漏问题。

因此,可以说 std::weak_ptr 并非直接打破循环引用,而是通过协助 std::shared_ptr 来实现循环引用的解决方案。它在不增加引用计数的前提下,提供了一种访问被 std::shared_ptr 管理的对象的方式,并且不会阻止对象的销毁。

# unique_ptr

它和 sp 其实差不多了,区别就是 ownership 的独占,which 通过禁用拷贝构造和赋值构造来实现,所以你不能拷贝或赋值一个 up 给另一个 up:

1
2
3
4
unique_ptr<Thing> p1 (new Thing); // p1 owns the Thing
unique_ptr<Thing> p2(p1); // error - copy construction is not allowed.
unique_ptr<Thing> p3; // an empty unique_ptr;
p3 = p1; // error, copy assignment is not allowed

由于禁用拷贝构造,所以如果要把 up 作为函数参数进行传参时,只能 pass by reference,不能 pass by value。

1、所有权转移
而由于 up 对于对象的所有权是独占的,所以有时需要转移所有权,这依赖于移动语义,使用移动构造和移动赋值函数。

其中,我们知道,函数的返回值是右值,所以 up 的这种移动语义可以用于我们在函数中返回一个局部的 up 给外层调用该函数的 up,实现移动:

1
2
3
4
5
6
7
8
9
10
11
12
13
//create a Thing and return a unique_ptr to it:
unique_ptr<Thing> create_Thing() {
unique_ptr<Thing> local_ptr(new Thing);
return local_ptr; // local_ptr will surrender ownership
}

void foo() {
unique_ptr<Thing> p1(create_Thing()); // move ctor from returned rvalue
// p1 now owns the Thing
unique_ptr<Thing> p2; // default ctor'd; owns nothing
p2 = create_Thing(); // move assignment from returned rvalue
// p2 now owns the second Thing
}

2、所有权的显式转移
通过 std::move() 函数来显式地进行所有权的转移:

1
2
3
4
5
6
unique_ptr<Thing> p1(new Thing); // p1 owns the Thing
unique_ptr<Thing> p2; // p2 owns nothing
// invoke move assignment explicitly
p2 = std::move(p1); // now p2 owns it, p1 owns nothing
// invoke move construction explicitly
unique_ptr<Thing> p3(std::move(p2)); // now p3 owns it, p2 and p1 own nothing

另外,前面在讲 sp 的时候讲到了有个函数 make_shared ,而 C++11 居然没有相对应的 make_unique ,直到 C++14 的时候才引入了 make_unique 函数。不过, make_unique 不会有性能上的提升,因为 up 并不会分配管理对象,所以没有额外的内存分配,不会有两次内存分配,就不会有性能低的问题。—— 可能这就是为什么 C++11 引入了 make_shared 却没有同时引入 make_unique 的原因吧。

# shared_ptr

Shared pointer,a class template:

1
2
#include <memory>
template <class T> class shared_ptr;

简单来说,share pointer 具备引用计数、自动释放的特点。多个  shared_ptr  指向同一块内存时,每多一个指针指向该内存,引用数相应自增,反之亦然。当一块内存的  shared_ptr  引用数减少为 0 时,自动释放该内存。

即,我们可以通过智能指针来申请动态内存,其会有一个相应的引用计数(申请时初始值为 1,表明当前这个  shared_ptr  指向该内存)。每当有新的智能指针指向该内存时,引用计数自增。

https://cplusplus.com/reference/memory/shared_ptr/

智能指针的创建:

空智能指针:

1
2
3
std::shared_ptr<int> p1;	//指向 int 类型的智能指针,此时不传入任何实参,为空指针
std::shared_ptr<int> p2(nullptr); //传入空指针 nullptr
//空指针的引用计数为 0,此时并未指向任何内存,合情合理

非空智能指针:

1
2
3
4
5
std::shared_ptr<int> p3(new int(10));	//指向一个值为 3 的 int 型的内存
//此时引用计数为 1

//另外,也可以通过 C++11 提供的模板函数来初始化智能指针
std::shared_ptr<int> p4 = std::make_share<int>(10);

构造函数(拷贝构造和移动构造):

1
2
3
4
5
6
7
//拷贝构造函数
std::shared_ptr<int> p5(p4);
std::shared_ptr<int> p6 = p5;

//移动构造
std::shared_ptr<int> p7(std::move(p6));
std::shared_ptr<int> p8 = std::move(p7);

成员方法:

  • swap()
    交换两个相同类型  shared_ptr  的内容

  • reset()
    当该函数没有传入实参时,作用是将当前  shared_ptr  所指内存的引用计数减 1,同时将当前指针对象重置为一个空指针;

当该函数传入一个实参(一个新申请的堆空间)时,则当前指针对象会指向该新申请的堆空间且引用计数初始值为 1,同时,原指向的堆空间(如果有)的引用计数自然而然减 1,如果减 1 后为 0,则释放该内存

  • get()
    获得  shared_ptr  对象内部包含的普通指针

  • use_count()
    返回当前智能指针所指向内存的引用计数值,即所有指向该内存的智能指针的数量

  • unique()
    判断当前智能指针指向的堆内存,是否不再有其他  shared_ptr  指向它

参考链接:
https://cplusplus.com/reference/memory/shared_ptr/reset/
http://c.biancheng.net/view/7898.html
http://c.biancheng.net/view/430.html
https://blog.csdn.net/ff_gogo/article/details/123512482
https://www.cnblogs.com/dream397/p/14620324.html

实际应用:

1
2
3
4
5
6
7
8
9
10
m_dds_ctx.reset(new minieye::DDS::Context(FLAGS_config_json_radar, true));
...
if (radar_topic != nullptr)
{
std::shared_ptr<minieye::DDS::Reader> r(new minieye::DDS::Reader(
m_dds_ctx.get(), radar_topic->topic, OnRecvDDS, this));

m_readers = r;
}
...

# deleter 删除器

对于自定义的类,我们知道存在构造函数和析构函数,析构函数能够在对象销毁时被自动调用,结合智能指针则能够实现自动管理,让其在引用计数减为 0 时自动调用析构函数释放资源。

但是,对于内置的类型,比如 int、float 这种,并不存在任何析构函数,所以无论我们是否使用智能指针,我们都不能很好地调用所谓的 “析构函数” 来释放资源。比如:

1
std::shared_ptr<int> num{new int(5)};

针对这种情况,我们需要用到 deleter。

在智能指针中,可以指定一个删除器,用于在引用计数减为 0 时调用该删除器,进行资源的释放。以 shared_ptr 举例,它的其中两种构造函数如下:

1
2
3
template <class U, class D> shared_ptr(U* p, D del);
template <class D> shared_ptr(nullptr_t p, D del);
#其中 del 是一个删除器

deleter 可以是以下类型之一:

  1. 函数指针:指向一个函数,该函数会在智能指针的资源需要释放时被调用。
  2. 函数对象(Functor):是一个类对象,重载了函数调用运算符 operator() ,可以像函数一样调用。
  3. Lambda 表达式:一种匿名函数,可以用作智能指针的 deleter

于是就有了这种智能指针的构造方式:

1
2
3
4
5
// 使用 Lambda 表达式作为 deleter 的 std::shared_ptr
std::shared_ptr<MyClass> sharedPtr(new MyClass(24), [](MyClass* ptr) {
std::cout << "Lambda deleter is called" << std::endl;
delete ptr;
});

整体示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <memory>
using namespace std;

//Custom deleter function declaration
void my_deleter(int* ptr){
cout << "Deleting integer pointer with value: " << *ptr << endl;
delete ptr;
cout << "Delete Successful.";
}


int main()
{
//int type smart pointer object
shared_ptr<int> ptr {new int{5}, my_deleter};
return 0;
}

大概是这样。

这里需要注意的是,我们的 shared_ptr 的构造函数存在几种形式:

1
2
3
4
5
template< class Y >
explicit shared_ptr( Y* ptr );

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

对于前者,当引用计数值为 0 时会使用 delete ptrdelete[] ptr ,具体取决于 T 是否为数组类型。而对于后者,则是通过传入一个用户自定义的删除器,在引用计数值为 0 自动调用。详细信息见:shared_ptr

但是由此又似乎会得出结论,哪怕我们没有指定删除器,利用原本的 delete 语法也能处理基本类型嘛?再者,当使用智能指针管理基础类型时,真的不能有效释放内存吗?sus

# Q&A

智能指针如何判空?

我们知道,对于裸指针,我们可以直接在 if 条件语句中将其用作条件进行判空,如:

1
2
3
4
5
int* rp = new int(5);

if(rp) {
//...
}

同样的,对于智能指针,也能这么操作。

智能指针可以使用 operator bool()get() 方法来进行空指针判断。以下是两种常用的方法:

  1. 使用 operator bool()

std::shared_ptrstd::unique_ptr 都定义了 operator bool() ,可以用于将智能指针直接用于布尔上下文中进行判空。

1
2
3
4
5
6
7
8
9
std::shared_ptr<int> ptr = std::make_shared<int>(42);

if (ptr) {
// 指针不为空
// 执行代码...
} else {
// 指针为空
// 执行其他代码...
}

  1. 使用 get() 方法:

get() 方法返回底层裸指针,你可以将其与 nullptr 比较来进行判空。

1
2
3
4
5
6
7
8
9
std::shared_ptr<int> ptr = std::make_shared<int>(42);

if (ptr.get() != nullptr) {
// 指针不为空
// 执行代码...
} else {
// 指针为空
// 执行其他代码...
}

不过第二种方法有点没必要,好好的智能指针不用,还把它转化成裸指针,何苦呢

另外,这里的 operator bool() 是操作符重载,具体地:

operator bool() 是一个类型转换运算符,用于将类对象转换为布尔值。在 C++ 中,运算符重载允许程序员自定义类对象在特定上下文中的操作行为,而 operator bool() 是其中一种常见的运算符重载。

运算符重载的语法如下:

1
2
3
4
5
6
7
8
9
class MyClass {
public:
// 类型转换运算符的重载
explicit operator bool() const {
// 返回用于将对象转换为布尔值的逻辑表达式
// 返回 true 表示对象非空,返回 false 表示对象为空
return someCondition; // 替换为实际的逻辑表达式
}
};

在上述示例中, operator bool() 被定义为将 MyClass 类的对象转换为布尔值。注意其中的 explicit 关键字,它表示这个转换是显式的,防止隐式转换。

对于智能指针类(如 std::shared_ptrstd::unique_ptr ), operator bool() 的实现通常是检查底层指针是否为 nullptr 。这样,当智能指针指向有效对象时,转换结果为 true ,指向空对象时,转换结果为 false 。这种设计使得智能指针在条件语句中的使用更加自然,就像原始指针一样。

以下是智能指针 operator bool() 的典型实现:

1
2
3
4
5
6
7
8
9
10
class SmartPtr {
public:
// 类型转换运算符的重载
explicit operator bool() const {
return ptr != nullptr;
}

private:
int* ptr; // 以 int* 为例,实际上可能是其他类型
};

这使得你可以像下面这样使用智能指针:

1
2
3
4
5
6
SmartPtr ptr = ...;  // 通过某种方式初始化
if (ptr) {
// 智能指针不为空,执行代码...
} else {
// 智能指针为空,执行其他代码...
}

这种运算符重载的使用,使得在条件语句中判空更加直观,提高了代码的可读性。

# 相关阅读

stack overflow:如何判断我的 shared_ptr 被谁持有?

更新于

请我喝杯咖啡吧~

Rick 微信支付

微信支付

Rick 支付宝

支付宝