boost程序完全开发指南之shared_ptr

来源:互联网 发布:制造业数据分析 编辑:程序博客网 时间:2024/06/05 15:19

由于工作需要最近在自学boost(C++准标准库),学习中发现boost真的非常强大,于是把读书心得与读书笔记记于此,一方面以备日后回忆,另一方面分享给大家希望对大家会有所帮助。如果总结有什么不当之处,欢迎批评指正:

boost库中提供六种智能指针:scoped_ptr,scoped_array,shared_ptr,shared_array,weak_ptr和intrusive_ptr。都是异常安全的。其中shared_ptr和weak_ptr已被收入到C++新标准的TR1库中。使用它们需要:

#include<boost/smart_ptr.hpp>

using namespace boost;

这里我们只介绍shared_ptr。shared_ptr是一个最像指针的“智能指针”,是boost.smart_ptr库中最有价值、最重要的组成部分,也是最有用的。它包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针,可以被自由的拷贝和赋值,在任意地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全的放在标准容器中。

为什么使用智能指针?

我们都知道在堆上分配的内存需要我们手动释放即new之后要delete,但是我们回忆一下有没有忘记delete的时候,即使我们记性很好但是会不会还出现内存泄漏的情况呢?请看下面的例子:

int *p = new class_need_resource;

... //可能发生异常导致资源泄露

delete p;

智能指针可以在退出作用域时,不管是正常流程离开或是因异常离开总调用delete来析构在堆上动态分配的对象。shared_ptr的构造函数接受new操作符或者对象工厂创建出的对象指针作为参数,从而代理了原始指针,虽然他是一个对象,但因为重载了operator*和operator->,其行为非常类似指针,可以把它用在大多数普通指针可用的地方。当退出作用域时或者发生异常时,C++语言保证会销毁shared_ptr对象,调用shared_ptr的析构函数,进而使用delete操作符删除原始指针释放资源。下面我们就来详细的学习一下shared_ptr:

类摘要:

template<class T>
class shared_ptr
{
private:
T* p;
public:
shared_ptr();
template<class Y> explicit shared_ptr(Y* p);
shared_ptr(const shared_ptr& r);
template<class Y,class D> shared_ptr(Y* p,D d);
~shared_ptr();

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

T& operator*()const;
T* operator->()const;
T* get()const;
long use_count()const;
bool unique()const;
};

1、get()可以获得原始指针

2、shared_ptr有多种形式的构造函数,应用于各种可能的情形:

1.无参的shared_ptr()创建一个持有空指针的shared_ptr;

2.shared_ptr(Y*p)获得指向类型T的指针p的管理权,同时引用计数置为1.这个构造函数要求Y类型必须能够转换为T类型

3.shared_ptr(std::auto_ptr<Y>&r)从一个auto_ptr获得指针的管理权,引用计数置为1,同时auto_ptr自动失去管理权

4.shared_ptr=赋值操作符可以从另为一个shared_ptr或auto_ptr获得指针的管理权,其行为同构造函数。

5.shared_ptr(Y*p,D d)行为类似shared_ptr(Y*p),但使用参数d指定了析构时的定制删除器,而不是简单的delete。

3、reset()函数作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。带参数的reset()则类似相同形式的构造函数,原指针引用计数减1的同时改为管理另一指针。

计数测试实例:

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

void Test_Boost_Shared_Ptr()
{
shared_ptr<Foo> ptr1(new Foo("Foo1"));
assert(ptr1.use_count()==1);

shared_ptr<Foo> ptr2 = ptr1;
assert(ptr1.use_count() == ptr2.use_count());
assert(ptr2 == ptr1);
assert(ptr2.use_count()==2);

shared_ptr<Foo> ptr3 = ptr1;
assert(ptr3.use_count() == ptr2.use_count());
assert(ptr3 == ptr1);
assert(ptr3.use_count()==3);
ptr3.reset();//将ptr3的引用计数减一,重置为NULL
assert(ptr3.use_count()==0);
assert(ptr3.get()==NULL);//此时不会调用析构函数因为ptr1和ptr2还指向原指针
assert(ptr2.use_count()==2);

ptr2.reset(new Foo("Foo2"));
assert(ptr1.use_count()==1);
assert(ptr2.use_count()==1);
}

测试结果:

Destructing Foo with name = Foo2

Destructing Foo with name = Foo1

4、两个专门的函数检查引用计数。unique()在shared_ptr是指真的唯一所有者时返回true。use_count()返回当前指针的引用计数,它仅仅用于测试或者调试,他不提供高效的操作,而且有的时候可能是不可用的。

5、shared_ptr还支持比较运算,比较两个shared_ptr的相等和不相等原理:a.get()==b.get()。还提供<比较所以可以用于标准容器(set和map)。

6、类型转换(72页)

shared_ptr的应用:

1、几乎100%可以在new出现的地方用shared_ptr接受new动态分配的结果。

例如:shared_ptr<int> spi(new int(10));

这样问题就来了,我们已经习惯new和delete同时出现了,这里尽管new被包上了但是没有delete总觉得缺少对称美。好吧,工厂函数的使用来医治我们的强迫症。

工厂函数

①头文件:<boost/make_shared.hpp>。命名空间boost

②声明:

template<class T,class...Args>

shared_ptr<T> make_shared(Args&&...args);

make_shared()函数可以接受最多10个参数,然后把它们传递给类型T的构造函数,创建一个shared_ptr<T>的对象并返回。make_shared()函数要比直接创建shared_ptr对象的方式快且高效,因为它内部仅分配一次内存,消除了shared_ptr构造时的开销。用例如下:

shared_ptr<string> sp = make_shared<string>("make-shared");//创建string的共享指针

2、应用于容器:

①将容器作为shared_ptr管理的对象,如shared_ptr<list<T>>使容器可以被安全的共享,用法与普通shared_ptr没有区别

②将shared_ptr作为容器的元素如vector<shared_ptr<T>>

3、应用于桥接模式

桥接模式是一种结构型设计模式,他把泪的具体实现细节对用户隐藏起来,以达到类之间的最小偶和关系。它可以将头文件的依赖关系降到最小,减少编译时间,而且可以不使用虚函数实现多肽。桥接模式非常有用,它可以任意改变具体的实现而外界对此一无所知,也减少了源文件之间的编译依赖,是程序获得了更多的灵活性。而shared_ptr是实现他的最佳工具之一,它解决了指针的共享和引用计数问题。

//c++桥接设计模式来封装现有的C函数,隐藏实现细节
class FileSharedPtr
{
private:
class impl//很重要的一点,向前申明实现类,具体实现在.cpp文件中
{
private:
FILE* f;
impl(impl const&){}
impl& operator=(impl const&){}
public:
impl(char const* name,char const* mode)
{
fopen_s(&f,name,mode);
}
~impl()
{
int result = fclose(f);
cout<<"invoke FileSharedPtr::impl 析构函数result="<<result<<endl;
}
void read(void* data,size_t size)
{
fread(data,size,1,f);
}
};
shared_ptr<impl> pimpl;//shared_ptr作为私有成员变量
public:
FileSharedPtr(char const* name,char const* mode):pimpl(new FileSharedPtr::impl(name,mode)){}
void Read(void*data,size_t size)
{
pimpl->read(data,size);
cout<<data<<endl;
}
};

void Test_File_Ptr()
{
FileSharedPtr ptr("memory.txt","r");
char data[51] = {0};
ptr.Read(data,50);
}

4、应用于工厂模式

工厂模式是一种创建型设计模式,这个模式包装了new操作符的使用,使对象的创建工作集中在工厂类或者工厂函数中,从而更容易适应变化,make_shared()就是工厂模式的一个很好的例子。

//shared_ptr应用于工厂模式实例:
class IPrinter
{
public:
virtual void Print() = 0;
protected:
//保护为了防止外界删除智能指针管理的指针
virtual ~IPrinter(){cout<<"invoke IPrinter virtual 析构函数"<<endl;}
};


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

fopen_s(&f,path,mode);

}
~Printer(){
int result = fclose(f);
cout<<"~Printer() result = "<<result<<endl;
}
void Print()
{
char data[51] = {0};
fread(data,50,1,f);
cout<<data<<endl;
}
};
//工厂方法,创建IPrinter智能指针
shared_ptr<IPrinter> CreatePrinter()
{
shared_ptr<IPrinter> ptr(new Printer("memory.txt","r"));
return ptr;
}

void Test_Printer()
{
shared_ptr<IPrinter> ptr = CreatePrinter();
ptr->Print();
}

由于基类IPrinter的析构函数是保护的,所以用户不能做出任何对指针的破坏行为,即使是用get()获得了原始指针:

shared_ptr<IPrinter> ptr = CreatePrinter();

IPrinter* q = ptr.get();//正确

delete q;//错误 因为IPrinter构造函数为保护成员只能自己或者子类内部调用。

但是有粗鲁的办法:

Printer* q = (Printer*)ptr;

delete q;//正确 因为子类析构函数共有

5、定制删除器

shared_ptr(Y*p,D d)的第一个参数是要被管理的指针,他的含义与其他构造函数的参数相同。而第二个删除器参数d则告诉shared_ptr在析构时不是使用delete来操作指针p,而要用d来操作,即把delete p换成d(p)。这里删除器d可以是一个函数对象,也可以是一个函数指针,只要他能够像函数那样被调用,使得d(p)成立即可。对删除器的要求是它必须是可拷贝的,行为必须也像delete那样,不能抛出异常。为了配合删除器的工作,shared_ptr提供一个自由函数get_deleter(shared_ptr<T> const & p),他能返回删除器的指针。使用如下:

shared_ptr<FILE> fp(fopen("readme.txt","r"),fclose);//当离开作用域时shared_ptr会自动调用fclose关闭文件

有了删除起的概念使shared_ptr不仅仅能够管理内存资源,而且成为一个”万能“的资源管理工具。

1.内存泄漏:在堆中分配的资源未释放。

2.野指针:初始化时未赋值或者delete后没将其值设为NULL

3.访问越界:

为了管理内存等资源,C++程序员通常采用RAII机制。

RAII机制(资源获取即初始化):在使用资源的类的构造函数中申请资源,然后使用,最后在析构函数中释放资源。

boost使用实例与注意:

1、下面代码会导致堆内存破坏而引起程序崩溃 因为ptr1和ptr2指向同一块内存函数结束后释放了两次
解决方案:
1.使用匿名内存分配限制其他shared_ptr多次指向同一内存数据
2.使用boost的make_shared模板函数例如:
shared_ptr<int> ptr1 = make_ptr<int>(100);需要<boost/make_shared.hpp>
shared_ptr<int> ptr2 = ptr1;

void test_boost_shared_ptr_crash()
{
Foo* pFoo = new Foo("error use");
shared_ptr<Foo> ptr1(pFoo);
shared_ptr<Foo> ptr2(pFoo);
int n = ptr1.use_count();//n=1
}

2、shared_ptr也有不足之处,他解决不了循环引用的问题。幸运的是boost为我们提供了weak_ptr弥补了它的美中不足

class Parent;
class Child;
class Parent
{
public:
~Parent()
{
cout<<"父类析构函数"<<endl;
}
public:
//shared_ptr<Child> child_ptr;
weak_ptr<Child> child_ptr;
void ParentPrint()
{
cout<<"ParentPrint()"<<endl;
}
};


class Child
{
public:
~Child()
{
cout<<"子类析构函数"<<endl;
}
public:
//shared_ptr<Parent> parent_ptr;
weak_ptr<Parent> parent_ptr;
};


void Test_Parent_Child_Ref()
{
shared_ptr<Parent> father(new Parent());
shared_ptr<Child> son(new Child());
father->child_ptr = son;
son->parent_ptr = father;
cout<<son->parent_ptr.lock().use_count()<<endl;
son->parent_ptr.lock()->ParentPrint();
}

shared_ptr不可以使用自增自减操作符(与真实指针的差别)

weak_ptr不能共享指针,不能操作资源(lock()解决了)构造函数和析构函数不能导致引用计数的增减所以可以解决循环引用问题

0 0
原创粉丝点击