Boost 智能指针(一)

引言:这是学习过程中的智能指针笔记,代码的解释写在了注释之间,为了方便及时查阅,以供大家学习。

内容包含:
1、初步认识 shared_ptr 的引用计数
2、shared_ptr 的缺陷 以及 如何使用 weak_ptr 避免缺陷
3、shared_ptr 的使用技巧


1、主函数
介绍了shared_ptr 的引用计数,shared_ptr 的缺陷 及使用技巧

#define _CRT_SECURE_NO_WARNINGS
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <string>
#include <assert.h>
#include <vector>
#include "FileSharedPtr.h"
#include "IPrinter.h"
using namespace std;

class Foo
{
public:
    Foo(const string & strname):m_strName(strname){}
    ~Foo()
    {
        cout << "Destructing Foo with name = " << m_strName << endl;
    }
private:
    string m_strName;
};

typedef boost::shared_ptr<Foo> FooPtr;

//---------------- shared_ptr 的引用计数----------------------//
void Test_Boost_Shared_Ptr()
{
    /// ptr1 获得 Foo1 指针的所有权
    FooPtr ptr1(new Foo("Foo1"));/// 引用计数为1
    assert(ptr1.use_count() == 1);

    /// ptr2 指向 ptr1
    FooPtr ptr2 = ptr1;/// 调用 shared_ptr 的赋值操作符,引用计数加1
    assert(ptr1.use_count() == ptr2.use_count());/// 两者引用计数相同
    assert(ptr1 == ptr2);/// shared_ptr 重载 == 操作符,等同于ptr1.get() == ptr2.get()
    assert(ptr1.use_count() == 2);/// 现在ptr1 和ptr2 都指向了Foo1,因此计数器都为2

    /// ptr3获得 Foo1 指针的所有权
    FooPtr ptr3 = ptr2;///引用计数加1
    assert(ptr1.use_count() == ptr2.use_count() && ptr1.use_count() == ptr3.use_count());
    assert(ptr1.use_count() == 3 && ptr2.use_count() == 3 && ptr3.use_count() == 3);
    assert(ptr1 == ptr2 && ptr1 == ptr3);

    /// 重置ptr3,测试reset()函数
    ptr3.reset();
    assert(ptr3.use_count() == 0 && ptr3.get() == NULL);
    cout << "ptr3 引用计数为0,get()指针指向NULL,但是不会调用析构函数,因为ptr1和ptr2都指向了原生指针" << endl;
    assert(ptr1.use_count() == ptr2.use_count() && ptr1.use_count() == 2);
    assert(ptr1 == ptr2 && ptr1 != ptr3);

    /// 前面ptr1和ptr2都指向了同一个对象,他们的引用计数都为2,现在创建一个ptr4,让其指向ptr1
    FooPtr ptr4 = ptr1; // 引用计数加一
    assert(ptr1 == ptr2 && ptr4 == ptr1);
    assert(ptr4.use_count() == 3 && ptr1.use_count() == 3 && ptr2.use_count() == 3);

    /// 现在转移ptr2的所有权到另一个Foo指针上去
    cout << "转移ptr2的拥有权到一个新的Foo指针上" << endl;
    ptr2.reset(new Foo("Foo2"));

    /// 在ptr2转移所有权后,ptr2计数器应该是1,并且ptr1和ptr4的计数器是2,并且ptr1 != ptr2
    assert(ptr2.use_count() == 1 && ptr1.use_count() == 2 && ptr4.use_count() == 2);
    assert(ptr1 != ptr2 && ptr1 == ptr4);

    ///此时ptr3因为被重置为0,并且get()为NULL了,我们再使用一个ptr5来指向ptr3也应该引用计数为0
    FooPtr ptr5 = ptr3;
    assert(ptr5.use_count() == 0 && ptr5.get() == NULL);
/*********************** 总结 ********************************
    运行到此时,程序即将结束,在退出作用于前会调用析构函数(引用计数为0,即调用析构函数)
    首先会打印出Destructing a Foo with name = Foo2
    ptr1 和 ptr4也会相继调用析构函数进行引用计数递减
    最终会打印出Destructing a Foo withname = Foo1
    内存释放完毕,这样就没有内存泄漏了
**************************************************************/ 
}

//---------------------shared_ptr 的缺陷------------------------//
void Test_Boost_Shared_ptr_crash()
{
    Foo * pFoo = new Foo("Foo1");
    shared_ptr<Foo> ptr1(pFoo);
    shared_ptr<Foo> ptr2(pFoo);
    cout << ptr1.use_count() << endl;
    cout << ptr2.use_count() << endl;
    /// 函数退出时会崩溃!,因为ptr1、ptr2两次释放同一内存而破坏堆,导致程序崩溃    

/******************** 总结 ******************************
shared_ptr  多次引用同一内存数据将导致程序崩溃
1)直接使用 boost::shared_ptr<int> ptr1(new int(100));
    使用匿名内存分配限制其他shared_ptr多次指向同一内存数据
    多次指向时使用 shared_ptr 的复制操作符或拷贝构造函数,例如 shared_ptr<int> ptr2 = ptr1;
    上面的匿名方式就是资源初始化既分配技术,直接在构造函数中分配内存
2)使用boost的 make_shared 模板函数,例如shared_ptr<int> ptr1 = boost::make_shared<int>(100)
    shared_ptr<int> ptr2 = ptr1;
****************************************************/
}

//----------------------shared_ptr 的使用技巧--------------------------//
/// shared_ptr 的使用技巧一:将shared_ptr用于标准容器库
/// (1)标准容器库作为shared_ptr管理的对象 (2)将 shared_ptr 作为容器的元素
void Test_Vector_Shared_Ptr()
{
    char name[32];
    vector<FooPtr> ptrs;
    for (int i = 0; i < 20; i++)
    {
        sprintf(name, "foo%d", i);
        ptrs.push_back(FooPtr(new Foo(name)));
    }/// 循环完毕:20个FooPtr的引用计数为 1

    for (int i = 0; i < 20; i++)
    {
        FooPtr ptr = ptrs[i];   /// 此时 ptr进行引用后,引用计数加1,此时引用计数为 2
        assert(ptrs[i].use_count() == 2);
    }/// 循环完毕,各自的引用计数为1.为什么不是2?因为 ptr 为局部变量,它的作用域
     /// 在循环内,循环完毕退出作用域的时候,会进行减1操作,所以 2-1=1

    for (int i = 0; i < 20; i++)
    {
        assert(ptrs[i].use_count() == 1);
    }

}/// 函数结束的时候,退出作用域,ptrs里面的FooPtr的引用计数会进行减1操作,最后的引用计数为0
 /// 引用计数为0后,自动调用析构函数释放内存

/// shared_ptr 的使用技巧二:以函数封住现有的c函数
typedef boost::shared_ptr<FILE> FilePtr;
void FileClose(FILE * f)
{
    fclose(f);
    cout << "调用fclose函数释放FILE资源" << endl;
}
FilePtr FileOpen(char const* path, char const* mode)
{
    //第二个参数是指,通过调用 FileClose 来释放第一个参数
    FilePtr fptr(fopen(path, mode), FileClose);
    //FilePtr fptr(fopen(path, mode), fclose);
    return fptr;
}
void FileRead(FilePtr& f, void* data, size_t size)
{
    cout << "use_count() = " << f.use_count() << endl;
    fread(data, 1, size, f.get());
}
void Test_C_File_Ptr()
{
    FilePtr ptr = FileOpen("memory.log", "r");
    FilePtr ptr2 = ptr;
    char data[512] = { 0 };
    FileRead(ptr, data, 512);
    cout << data << endl;
}

/// shared_ptr 使用技巧三:用c++桥接设计模式来封装现有的c函数
void Test_FileSharePtr()
{
    char data[512] = { 0 };
    FileSharedPtr pPile("memory.log", "r");
    pPile.Read(data, 512);
}

/// shared_ptr 使用技巧四:使用面向接口编程方式隐藏实现
/// 注意面向接口编程的思想 
void Test_Printer()
{
    PrinterPtr ptr = CreatePrinter();
    ptr->Print();
    PrinterPtr ptr2 = ptr;
    ptr2->Print();
}

int main() 
{
    //Test_Boost_Shared_Ptr();
    //Test_Boost_Shared_ptr_crash();
    //Test_Vector_Shared_Ptr();
    //Test_C_File_Ptr();
    //Test_FileSharePtr();
    Test_Printer();
    getchar();
    return 0;
}

主函数中使用到的 IPrinter类
IPrinter.h

#pragma once
#include <boost/shared_ptr.hpp>

class IPrinter
{
public:
    virtual void Print() = 0;
protected:
    // 受保护的析构函数,
    // 如果要调用析构函数,必须被继承,也不会被 delete 释放,可以防止用户不小心d elete
    virtual ~IPrinter()
    {
        printf("invoke IPrinter virtual析构函数\n");
    }
};

typedef boost::shared_ptr<IPrinter> PrinterPtr;
PrinterPtr CreatePrinter(); // 工厂方法,创建IPrinter智能指针

IPrinter.cpp

#include "IPrinter.h"

class Printer :public IPrinter
{
private:
    FILE* f;
public:
    Printer(const char* path, const char* mode)
    {
        f = fopen(path, mode);
    }

    ~Printer()
    {
        int result = fclose(f);
        printf("invoke Printer virtual析构函数:%d\n",result);
    }

    virtual void Print() override
    {
        char data[512] = { 0 };
        fread(data, 1, 512, f);
        printf("%s\n", data);
    }

    // 子类释放的时候,会自动调用父类的析构函数
    // 通过这种封装,同时就封装了 delete 的操作,只有调用我们的智能指针才能释放内存
    // 此时就实现了,通过智能指针来管理内存的功能,没有了原始指针的操作,也不会忘记没有释放指针导致的内存泄漏问题
};

PrinterPtr CreatePrinter()
{
    PrinterPtr ptr(new Printer("memory.log", "r"));
    return ptr;
}

主函数中使用到的 FileSharedPtr 类
FileSharedPtr.h

#pragma once
#include <boost/shared_ptr.hpp>

/// shared_ptr 使用技巧三:用c++桥接设计模式来封装现有的c函数
/// 注意桥接模式的用法
class FileSharedPtr
{
public:
    FileSharedPtr();
    ~FileSharedPtr(); 
    FileSharedPtr(char const* name, char const* mode);
    void Read(void* data, size_t size);
private:
    class impl;// 很重要一点,前向声明实现该类,具体实现在.cpp文件中,隐藏实现细节
    boost::shared_ptr<impl> pimpl;// shared_ptr 作为私有成员变量
};

FileSharedPtr.cpp

#include "FileSharedPtr.h"

class FileSharedPtr::impl
{
private:
    impl(impl const &) {}
    impl & operator= (impl const&) {}
    FILE* f;

public:
    impl(char const* name, char const* mode)
    {
        f = fopen(name, mode);
    }
    ~impl()
    {
        int result = fclose(f);
        printf("invoke FileSharedPtr::impl 析构函数 result = %d\n", result);
    }
    void read(void * data, size_t size)
    {
        fread(data, 1, size, f);
    }
};

FileSharedPtr::FileSharedPtr()
{
}

FileSharedPtr::~FileSharedPtr()
{
}

FileSharedPtr::FileSharedPtr(char const* name, char const* mode)
    : pimpl(new FileSharedPtr::impl(name, mode)) {}

void FileSharedPtr::Read(void* data, size_t size)
{
    pimpl->read(data, size);
}

/****************************************
这样的好处是:
上层类(FileSharedPtr)都是调用的实现类(impl)的具体的方法,
这样我们就把我们所有的实现都放在实现类中,上层类相当于一个接口类,
不进行相关操作,只提供相应的接口
*******************************************/

2、使用 weak_ptr 解决 shared_ptr 循环引用导致内存泄露问题

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <iostream>
using namespace std;

class Parent;
class Child;

class Parent
{
public:
    Parent(){}
    ~Parent(){cout << "父类析构函数被调用" << endl;}
public:
    boost::shared_ptr<Child> child_shared_ptr;
    boost::weak_ptr<Child> child_weak_ptr;
};

class Child
{
public:
    Child(){}
    ~Child(){cout << "调用child的析构函数" << endl;}
public:
    boost::shared_ptr<Parent> parent_shared_ptr;
    boost::weak_ptr<Parent> parent_weak_ptr;
};

/// 测试 shared_ptr 循环引用导致内存泄露
void Test_Parent_Child_Ref()
{
    boost::shared_ptr<Parent> father(new Parent());
    boost::shared_ptr<Child> son(new Child());
    father->child_shared_ptr = son;
    son->parent_shared_ptr = father;
/*****************************************************************
测试结果:会发现并没有调用相关的构造函数,导致内存泄露
原因:因为循环引用,导致father、son的引用计数都为2,退出作用域(就是退出此函数)的时候,
    引用计数都减一,此时彼此的引用计数为2-1=1,因为调用析构函数必须的引用计数为0,
    所以此时就无法调用析构函数,于是造成father和son所指向的内存得不到释放,导致内存泄露
*****************************************************************/
}

/// 测试 weak_ptr 解决循环引用和自引用导致的内存泄漏问题
void Test_Weak_ptr()
{
    boost::shared_ptr<Parent> father(new Parent());
    boost::shared_ptr<Child> son(new Child());
    father->child_weak_ptr = son;
    son->parent_weak_ptr = father;
    // 此时我们使用 lock 方法进行操作
    cout << father->child_weak_ptr.lock().use_count() << endl;
    cout << son->parent_weak_ptr.lock().use_count() << endl;
/*********************************************************************
lock()方法作用是转换为 shared_ptr:
函数原型:
shared_ptr<_Ty> lock() const _NOEXCEPT
{ 
    return (shared_ptr<_Ty>(*this, false)); 
}
**********************************************************************/
}

int main()
{
    Test_Parent_Child_Ref();
    getchar();
    return 0;
}

/***************************** 总结 *************************************
1、引用计数是很便利的内存管理机制,但是对于循环引用或自引用对象(例如链表或树节点)无法管理,
    此时用 weak_ptr 来解决这个限制
2、weak_ptr 并不能单独存在,它是与 shared_ptr 同时使用的,它更像 shared_ptr 的助手,而不是
    智能指针,因为它不具备智能指针的行为,没有重载 operator* 和 -> 操作符,这是特意的,这样
    他就不能共享指针,不能操作资源,这是它弱的原因。它最大的作用是协助 shared_ptr 工作,
    像旁观者那样观察资源的使用情况。
3、为什么 weak_ptr 能解决循环引用的问题?
    weak_ptr(弱引用)获得的是资源的观察权,它可以从一个 shared_ptr 或另一个 weak_ptr 构造,
    但 weak_ptr 并没有共享资源,它的构造并不会引起引用计数的增加,同时它的析构也不会引起引用
    计数的减少,它 仅仅! 是观察者。
4、weak_ptr 可以被用于标准容器库中的元素
    weak_ptr实现了拷贝构造函数和重载了复制操作符,因此weak_ptr可以被用于标准容器中的元素,
    例如:在一个树节点中声明子树节点:vector<boost::weak_ptr<Node>> children;
************************************************************************/

本文示例源码(VS2015):http://pan.baidu.com/s/1qYga9LM

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值