std::shared_ptr:共享的智能指针 ,std::unique_ptr:独占的智能指针 ,std::weak_ptr:弱引用的智能指针

C++ 中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存
C++11 中提供了三种智能指针,使用这些智能指针时需要引用头文件
std::shared_ptr:共享的智能指针 std::unique_ptr:独占的智能指针 std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的。

共享智能指针

1.1shared_ptr 的初始化

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类,如果要进行初始化有三种方式:

1、通过构造函数

2、std::make_shared 辅助函数

3、 reset 方法

共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数 use_count,函数原型如下:

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;

1.1.1 通过构造函数初始化

// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

示例

   #include <iostream>
    #include <memory>
    using namespace std;
    
    int main()
    {
        // 使用智能指针管理一块 int 型的堆内存
        shared_ptr<int> ptr1(new int(520));
        cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
        // 使用智能指针管理一块字符数组对应的堆内存
        shared_ptr<char> ptr2(new char[12]);
        cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
        // 创建智能指针对象, 不管理任何内存
        shared_ptr<int> ptr3;
        cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
        // 创建智能指针对象, 初始化为空
        shared_ptr<int> ptr4(nullptr);
        cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    
        return 0;
    }

ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 1
ptr3管理的内存引用计数: 0
ptr4管理的内存引用计数: 0

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数 + 1,如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数不会 + 1。另外,不要使用一个原始指针初始化多个 shared_ptr。

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);		// error, 编译不会报错, 运行会出错

1.1.2 通过拷贝和移动构造函数初始化

当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了。

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    //调用拷贝构造函数
    shared_ptr<int> ptr2(ptr1);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    shared_ptr<int> ptr3 = ptr1;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    //调用移动构造函数
    shared_ptr<int> ptr4(std::move(ptr1));
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    std::shared_ptr<int> ptr5 = std::move(ptr2);
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;

    return 0;
}

ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 2
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 3
ptr5管理的内存引用计数: 3

如果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,因此内存的引用计数不会变化。

1.1.3 通过 std::make_shared 初始化

通过 C++ 提供的 std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
//T:模板参数的数据类型
//Args&&... args :要初始化的数据,如果是通过 make_shared 创建对象,需按照构造函数的参数列表指定

示例

    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    
    class Test
    {
    public:
        Test() 
        {
            cout << "construct Test..." << endl;
        }
        Test(int x) 
        {
            cout << "construct Test, x = " << x << endl;
        }
        Test(string str) 
        {
            cout << "construct Test, str = " << str << endl;
        }
        ~Test()
        {
            cout << "destruct Test ..." << endl;
        }
    };
    
    int main()
    {
        // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
        shared_ptr<int> ptr1 = make_shared<int>(520);
        cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    
        shared_ptr<Test> ptr2 = make_shared<Test>();
        cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    
        shared_ptr<Test> ptr3 = make_shared<Test>(520);
        cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    
        shared_ptr<Test> ptr4 = make_shared<Test>("我是要成为海贼王的男人!!!");
        cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
        return 0;
    }

使用 std::make_shared() 模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。

1.1.4 通过reset 方法初始化

共享智能指针类提供的 std::shared_ptr::reset 方法函数原型如下:

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

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

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

ptr:指向要取得所有权的对象的指针
d:指向要取得所有权的对象的指针
aloc:内部存储所用的分配器

  #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    
    int main()
    {
        // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
        shared_ptr<int> ptr1 = make_shared<int>(520);
        shared_ptr<int> ptr2 = ptr1;
        shared_ptr<int> ptr3 = ptr1;
        shared_ptr<int> ptr4 = ptr1;
        cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
        cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
        cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
        cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    
        ptr4.reset();
        cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
        cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
        cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
        cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    
        shared_ptr<int> ptr5;
        ptr5.reset(new int(250));
        cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
    
        return 0;
    }

ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4
ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0
ptr5管理的内存引用计数: 1

对于一个未初始化的共享智能指针,可以通过 reset 方法来初始化,当智能指针中有值的时候,调用 reset 会使引用计数减 1。

1.1.5 获取原始指针

对应基础数据类型来说,通过操作智能指针和操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么就需要取出原始内存的地址再操作,可以调用共享智能指针类提供的 get () 方法得到原始地址,其函数原型如下:

T* get() const noexcept;

示例

 #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    
    int main()
    {
        int len = 128;
        shared_ptr<char> ptr(new char[len]);
        // 得到指针的原始地址
        char* add = ptr.get();
        memset(add, 0, len);
        strcpy(add, "我是要成为海贼王的男人!!!");
        cout << "string: " << add << endl;
        
        shared_ptr<int> p(new int);
        *p = 100;
        cout << *p.get() << "  " << *p << endl;
        
        return 0;
    }

1.2 指定删除器

当智能指针管理的内存对应的引用计数变为 0 的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的

  #include <iostream>
    #include <memory>
    using namespace std;
    
    // 自定义删除器函数,释放int型内存
    void deleteIntPtr(int* p)
    {
        delete p;
        cout << "int 型内存被释放了...";
    }
    
    int main()
    {
        shared_ptr<int> ptr(new int(250), deleteIntPtr);
        return 0;
    }

删除器函数也可以是 lambda 表达式,因此代码也可以写成下面这样:

int main()
{
    shared_ptr<int> ptr(new int(250), [](int* p) {delete p; });
    return 0;
}

在 C++11 中使用 shared_ptr 管理动态数组时,需要指定删除器,因为 std::shared_ptr的****默认删除器不支持数组对象,具体的处理代码如下

int main()
{
    shared_ptr<int> ptr(new int[10], [](int* p) {delete[]p; });
    return 0;
}

在删除数组内存时,除了自己编写删除器,也可以使用 C++ 提供的 std::default_delete() 函数作为删除器,这个函数内部的删除功能也是通过调用 delete 来实现的,要释放什么类型的内存就将模板类型 T 指定为什么类型即可。具体处理代码如下:

int main()
{
    shared_ptr<int> ptr(new int[10], default_delete<int[]>());
    return 0;
}

另外,我们还可以自己封装一个 make_shared_array 方法来让 shared_ptr 支持数组,代码如下:

 #include <iostream>
    #include <memory>
    using namespace std;
    
    template <typename T>
    shared_ptr<T> make_share_array(size_t size)
    {
        // 返回匿名对象
        return shared_ptr<T>(new T[size], default_delete<T[]>());
    }
    
    int main()
    {
        shared_ptr<int> ptr1 = make_share_array<int>(10);
        cout << ptr1.use_count() << endl;
        shared_ptr<char> ptr2 = make_share_array<char>(128);
        cout << ptr2.use_count() << endl;
        return 0;
    }

2. 独占的智能指针

2.1 初始化

std::unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个 unique_ptr 赋值给另一个 unique_ptr

// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
// error, 不允许将一个unique_ptr赋值给另一个unique_ptr
unique_ptr<int> ptr2 = ptr1;

std::unique_ptr 不允许复制,但是可以通过函数返回给其他的 std::unique_ptr,还可以通过 std::move 来转译给其他的 std::unique_ptr,这样原始指针的所有权就被转移了,这个原始指针还是被独占的。

#include <iostream>
#include <memory>
using namespace std;

unique_ptr<int> func()
{
    return unique_ptr<int>(new int(520));
}

int main()
{
    // 通过构造函数初始化
    unique_ptr<int> ptr1(new int(10));
    // 通过转移所有权的方式初始化
    unique_ptr<int> ptr2 = move(ptr1);
    unique_ptr<int> ptr3 = func();

    return 0;
}

unique_ptr 独占智能指针类也有一个 reset 方法,函数原型如下:

void reset( pointer ptr = pointer() ) noexcept;

使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针。

int main()
{
    unique_ptr<int> ptr1(new int(10));
    unique_ptr<int> ptr2 = move(ptr1);

    ptr1.reset();//除对原始内存的管理
    ptr2.reset(new int(250));//重新指定智能指针管理的原始内存

    return 0;
}

如果想要获取独占智能指针管理的原始地址,可以调用 get () 方法,函数原型如下:

   pointer get() const noexcept;
int main()
{
    unique_ptr<int> ptr1(new int(10));
    unique_ptr<int> ptr2 = move(ptr1);

    ptr2.reset(new int(250));
    cout << *ptr2.get() << endl;	// 得到内存地址中存储的实际数值 250

    return 0;
}

2.2 删除器

unique_ptr 指定删除器和 shared_ptr 指定删除器是有区别的,unique_ptr 指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器。

示例

shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// ok
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// error

int main()
{
    using func_ptr = void(*)(int*);
    unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });

    return 0;
}

在上面的代码中第 7 行,func_ptr 的类型和 lambda表达式的类型是一致的。在 lambda 表达式没有捕获任何变量的情况下是正确的,如果捕获了变量,编译时则会报错:

int main()
{
    using func_ptr = void(*)(int*);
    unique_ptr<int, func_ptr> ptr1(new int(10), [&](int*p) {delete p; });	// error
    return 0;
}

上面的代码中错误原因是这样的,在 lambda 表达式没有捕获任何外部变量时,可以直接转换为函数指针,一旦捕获了就无法转换了,如果想要让编译器成功通过编译,那么需要使用可调用对象包装器来处理声明的函数指针:

int main()
{
    using func_ptr = void(*)(int*);
    unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });
    return 0;
}

3. 弱引用智能指针(不共享指针,不操作资源,监视shared_ptr)

3.1 基本使用方法

弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 *->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在

3.1.1 初始化

// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
// 通过shared_ptr对象构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;

示例

  #include <iostream>
    #include <memory>
    using namespace std;
    
    int main() 
    {
        shared_ptr<int> sp(new int);
    
        weak_ptr<int> wp1;      //构造了一个空 weak_ptr 对象
        weak_ptr<int> wp2(wp1);//通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
        weak_ptr<int> wp3(sp);//通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
        weak_ptr<int> wp4;
        wp4 = sp; //通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
        weak_ptr<int> wp5;
        wp5 = wp3;// 通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
        
        return 0;
    }

3.1.2 其他常用方法

3.1.2.1 use_count()

通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数,函数原型如下:

// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;

修改一下上面的测试程序,添加打印资源引用计数的代码:

#include <iostream>
#include <memory>
using namespace std;

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

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;

    cout << "use_count: " << endl;
    cout << "wp1: " << wp1.use_count() << endl;//0
    cout << "wp2: " << wp2.use_count() << endl;//0
    cout << "wp3: " << wp3.use_count() << endl;//1
    cout << "wp4: " << wp4.use_count() << endl;//1
    cout << "wp5: " << wp5.use_count() << endl;//1
    return 0;
}

通过打印的结果可以知道,虽然弱引用智能指针 wp3、wp4、wp5 监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了 weak_ptr只是监测资源,并不管理资源。

3.1.2.2 expired()

通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放,函数原型如下:

// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;

函数的使用方法如下:

 #include <iostream>
    #include <memory>
    using namespace std;
    
    int main() 
    {
        shared_ptr<int> shared(new int(10));
        weak_ptr<int> weak(shared);
        cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
    		//weak is not expired
        shared.reset();
        cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
    		//weak is expired
        return 0;
    }

weak_ptr 监测的就是 shared_ptr 管理的资源,当共享智能指针调用 shared.reset(); 之后管理的资源被释放,因此 weak.expired() 函数的结果返回 true,表示监测的资源已经不存在了。

3.1.2.3 lock()

通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象,函数原型如下:

 shared_ptr<element_type> lock() const noexcept;

函数的使用方法如下

 #include <iostream>
    #include <memory>
    using namespace std;
    
    int main()
    {
        shared_ptr<int> sp1, sp2;
        weak_ptr<int> wp;
    
        sp1 = std::make_shared<int>(520);
        wp = sp1;
        sp2 = wp.lock();
        cout << "use_count: " << wp.use_count() << endl;//2
    
        sp1.reset();
        cout << "use_count: " << wp.use_count() << endl;//1
    
        sp1 = wp.lock();
        cout << "use_count: " << wp.use_count() << endl;//2
    
        cout << "*sp1: " << *sp1 << endl;//520
        cout << "*sp2: " << *sp2 << endl;//520
    
        return 0;
    }

sp2 = wp.lock(); 通过调用 lock() 方法得到一个用于管理 weak_ptr 对象所监测的资源的共享智能指针对象,使用这个对象初始化 sp2,此时所监测资源的引用计数为 2 sp1.reset(); 共享智能指针 sp1 被重置,weak_ptr 对象所监测的资源的引用计数减 1 sp1 = wp.lock();sp1 重新被初始化,并且管理的还是 weak_ptr 对象所监测的资源,因此引用计数加 1 共享智能指针对象 sp1 和 sp2 管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是 520

3.1.2.4 reset()

通过调用 std::weak_ptr 类提供的 reset() 方法来清空对象,使其不监测任何资源,函数原型如下:

void reset() noexcept;

示例

   #include <iostream>
    #include <memory>
    using namespace std;
    
    int main() 
    {
        shared_ptr<int> sp(new int(10));
        weak_ptr<int> wp(sp);
        cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;
    		//wp is not expired
        wp.reset();
        cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;
    		//wp is expired
        return 0;
    }

weak_ptr 对象 sp 被重置之后 wp.reset(); 变成了空对象,不再监测任何资源,因此 wp.expired() 返回 true

3.2 返回管理this 的 shared_ptr

如果在一个类中编写了一个函数,通过这个得到管理当前对象的共享智能指针,我们可能会写出如下代码:

  #include <iostream>
    #include <memory>
    using namespace std;
    
    struct Test
    {
        shared_ptr<Test> getSharedPtr()
        {
            return shared_ptr<Test>(this);
        }
        
        ~Test()
        {
            cout << "class Test is disstruct ..." << endl;
        }
    
    };
    
    int main() 
    {
        shared_ptr<Test> sp1(new Test);
        cout << "use_count: " << sp1.use_count() << endl;
        shared_ptr<Test> sp2 = sp1->getSharedPtr();
        cout << "use_count: " << sp1.use_count() << endl;
        return 0;
    }

执行上面的测试代码,运行中会出现异常,在终端还是能看到对应的日志输出:
use_count: 1
use_count: 1
class Test is disstruct …
class Test is disstruct …

1、通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和 sp2,这二者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后 this 将被构造的两个智能指针各自析构,导致重复析构的错误。

2、这个问题可以通过 weak_ptr 来解决,通过 wek_ptr 返回管理 this 资源的共享智能指针对象 shared_ptr。C++11 中为我们提供了一个模板类叫做 std::enable_shared_from_this,这个类中有一个方法叫做 shared_from_this(),通过这个方法可以返回一个共享智能指针在函数的内部就是使用 weak_ptr 来监测 this 对象,并通过调用 weak_ptrlock() 方法返回一个 shared_ptr 对象

修改之后的代码

  #include <iostream>
    #include <memory>
    using namespace std;
    
    struct Test : public enable_shared_from_this<Test>
    {
        shared_ptr<Test> getSharedPtr()
        {
            return shared_from_this();
        }
        ~Test()
        {
            cout << "class Test is disstruct ..." << endl;
        }
    };
    
    int main() 
    {
        shared_ptr<Test> sp1(new Test);
        cout << "use_count: " << sp1.use_count() << endl;
        shared_ptr<Test> sp2 = sp1->getSharedPtr();
        cout << "use_count: " << sp1.use_count() << endl;
        return 0;
    }

use_count: 1
use_count: 2
class Test is disstruct …
在调用 enable_shared_from_this 类的 shared_from_this () 方法之前,必须要先初始化函数内部 weak_ptr 对象,否则该函数无法返回一个有效的 shared_ptr 对象

3.3 解决循环引用问题(智能指针如果循环引用会导致内存泄漏)

示例

  #include <iostream>
    #include <memory>
    using namespace std;
    
    struct TA;
    struct TB;
    
    struct TA
    {
        shared_ptr<TB> bptr;
        ~TA()
        {
            cout << "class TA is disstruct ..." << endl;
        }
    };
    
    struct TB
    {
        shared_ptr<TA> aptr;
        ~TB()
        {
            cout << "class TB is disstruct ..." << endl;
        }
    };
    
    void testPtr()
    {
        shared_ptr<TA> ap(new TA);
        shared_ptr<TB> bp(new TB);
        cout << "TA object use_count: " << ap.use_count() << endl;//1
        cout << "TB object use_count: " << bp.use_count() << endl;//1
    
        ap->bptr = bp;
        bp->aptr = ap;
        cout << "TA object use_count: " << ap.use_count() << endl;//2
        cout << "TB object use_count: " << bp.use_count() << endl;//2
    }
    
    int main()
    {
        testPtr();
        return 0;
    }

在测试程序中,共享智能指针 ap、bp 对 TA、TB 实例对象的引用计数变为 2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 TA、TB 的实例对象不能被析构,最终造成内存泄露。通过使用 weak_ptr 可以解决这个问题,只要将类 TA 或者 TB 的任意一个成员改为 weak_ptr

修改之后的代码

 #include <iostream>
    #include <memory>
    using namespace std;
    
    struct TA;
    struct TB;
    
    struct TA
    {
        weak_ptr<TB> bptr;
        ~TA()
        {
            cout << "class TA is disstruct ..." << endl;
        }
    };
    
    struct TB
    {
        shared_ptr<TA> aptr;
        ~TB()
        {
            cout << "class TB is disstruct ..." << endl;
        }
    };
    
    void testPtr()
    {
        shared_ptr<TA> ap(new TA);
        shared_ptr<TB> bp(new TB);
        cout << "TA object use_count: " << ap.use_count() << endl;
        cout << "TB object use_count: " << bp.use_count() << endl;
    
        ap->bptr = bp;
        bp->aptr = ap;
        cout << "TA object use_count: " << ap.use_count() << endl;
        cout << "TB object use_count: " << bp.use_count() << endl;
    }
    
    int main()
    {
        testPtr();
        return 0;
    }

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct …
class TA is disstruct …

通过输出的结果可以看到类 TA 或者 TB 的对象被成功析构了。
上面程序中,在对类 TA 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 TB 的实例对象被析构。
在类 TB 的实例对象被析构的时候,内部的 aptr 也被析构,其对 TA 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 TA 对象的管理也解除了,内存的引用计数减为 0,类 TA 的实例对象被析构。

4. 指针空值类型 - nullptr

在 C++ 程序开发中,为了提高程序的健壮性,一般会在定义指针的同时完成初始化操作,或者在指针的指向尚未明确的情况下,都会给指针初始化为 NULL,避免产生野指针(没有明确指向的指针,操作也这种指针极可能导致程序发生异常)

4.1 C++98/03 标准中,将一个指针初始化为空指针的方式有 2 种:

char *ptr = 0;
char *ptr = NULL;

在底层源码中 NULL 这个宏是这样定义的:

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

也就是说如果源码是 C++ 程序 NULL 就是 0,如果是 C 程序 NULL 表示 (void*)0。这样做的原因是由于 C++ 中,void * 类型无法隐式转换为其他类型的指针,此时使用 0 代替 ((void *)0),用于解决空指针的问题。这个 0(0x0000 0000)表示的就是虚拟地址空间中的 0 地址,这块地址是只读的。

4.2 NULL和nullptr ( C++ 中将 NULL 定义为字面常量 0并不能保证在所有场景下都能很好的工作,比如,函数重载时,NULL 和 0 无法区分)

 #include <iostream>
    using namespace std;
    
    void func(char *p)
    {
        cout << "void func(char *p)" << endl;//void func(int p)
    }
    
    void func(int p)
    {
        cout << "void func(int p)" << endl;//void func(int p)
    }
    
    int main()
    {
        func(NULL);   // 想要调用重载函数 void func(char *p)
        func(250);    // 想要调用重载函数 void func(int p)
    
        return 0;
    }

通过打印的结果可以看到,虽然调用 func(NULL); 最终链接到的还是 void func(int p) 和预期是不一样的,在 C++ 中 NULL 和 0 是等价的。

出于兼容性的考虑,C++11 标准并没有对 NULL 的宏定义做任何修改,而是另其炉灶,引入了一个新的关键字 nullptrnullptr 专用于初始化空类型指针,不同类型的指针变量都可以使用 nullptr 来初始化:

int*    ptr1 = nullptr;
char*   ptr2 = nullptr;
double* ptr3 = nullptr;

对应上面的代码编译器会分别将 nullptr 隐式转换成 int*、char* 以及 double* 指针类型。

使用 nullptr 可以很完美的解决上边提到的函数重载问题:

   #include <iostream>
    using namespace std;
    
    void func(char *p)
    {
        cout << "void func(char *p)" << endl;//void func(char *p)
    }
    
    void func(int p)
    {
        cout << "void func(int p)" << endl;//void func(int p)
    }
    
    int main()
    {
        func(nullptr);
        func(250);
        return 0;
    }

nullptr 无法隐式转换为整形,但是可以隐式匹配指针类型。在 C++11 标准下,相比 NULL 和 0,使用 nullptr 初始化空指针可以令我们编写的程序更加健壮。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值