您的位置:首页 > 其它

Boost 智能指针(一)

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

内容包含:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息