boost-智能指针

时间:2023-03-09 02:50:31
boost-智能指针

使用boost的智能指针需要包含头文件"boost/smart_ptr.hpp",c++11中需要包含头文件<memory>

1、auto_ptr、scoped_ptr、scoped_array

①、auto_ptr是C++标准中的智能指针,在指针退出作用域的时候自动释放指针指向的内存,即使是异常退出的时候。auto_ptr实际上是一个对象,重载了operator*和operator->,且提供了一些成员函数,比如使用成员get()可以获得对应类型的原始指针。

auto_pt的特点是可以对其进行复制和赋值,但同一时刻只能有一个auto_ptr管理指针。

使用之前需要包含头文件<memory>,eg:

#include <memory>
#include <cassert>
int main()
{
std::auto_ptr<int> ap1(new int());
cout << *ap1 << endl; int* p = ap1.get();
cout << *p << endl; std::auto_ptr<int> ap2(ap1);//ap1失去管理权,不再拥有指针,ap2得到管理权
assert(ap1.get() == );//get()获得的指针为空 return ;
}

②、boost的scoped_ptr用法类似于auto_ptr,都不能用作容器的元素,不支持++、--等指针算数操作。

scoped_ptr的特点是拷贝构造函数和赋值操作符都是私有的,所以scoped_ptr不能进行复制和赋值操作,保证了对对象的唯一管理权。

#include "boost/smart_ptr.hpp"

int main()
{
boost::scoped_ptr<int> sp1(new int());
boost::scoped_ptr<int> sp2(sp1);//编译无法通过:不能转让sp1的管理权到sp2
boost::scoped_ptr<int> sp3(new int());
sp3 = sp1;//编译无法通过:不能转让sp1的管理权到sp3 return ;
}

③、如果需要指向new[]开辟的内存数组,应该使用scoped_array而不是scoped_ptr。scoped_array构造函数的参数必须是new[]返回的指针,支持使用[]来访问元素,但不支持*、->运算符。scoped_array也不支持拷贝,赋值。

2、shared_ptr、shared_array、weak_ptr

①、概述

shared_ptr没有scoped_ptr的限制,它可以被*的拷贝和赋值,也可以作为容器的元素。它是一种引用计数型的智能指针,当没有代码使用它时,即引用计数为0的时候自动删除动态分配的内存。

shared_ptr也支持*和->操作符,支持==比较操作(相当于a.get() == b.get()),提供隐式bool类型转换以判断指针的有效性,同样不提供指针的算数运算。

shared_ptr中的一些成员函数:

get():获取内部原始指针。

reset():重置shared_ptr,会导致引用计数减1。不带参数的reset()将shared_ptr重置为不指向任何对象,带参数的reset()用来将shared_ptr重置为指向新的对象。

unique():用来检测当前是不是指针的唯一管理者,即引用计数为1。

use_count():用来获取当前引用计数,但它一般在调试中使用,因为其效率很低,如果只是判断当前引用计数是否为1的话可以使用unique()来替换它。

将shared_ptr赋值为nullptr相当于调用reset()。

shared_ptr也提供了转换运算符dynamic_pointer_cast<>、static_pointer_cast<>、const_pointer_cast<>。

使用示例1:

#include "boost/smart_ptr.hpp"

int main()
{
boost::shared_ptr<int> spInt(new int());
if (spInt) //true
{
spInt.reset();
if (spInt) //false
;
} boost::shared_ptr<int> spInt1(new int()); //指针引用计数加1,现为1
assert(spInt1.unique()); boost::shared_ptr<int> spInt2(spInt1); //指针的引用计数加1,现为2 boost::shared_ptr<int> spInt3;
assert(!spInt3);
spInt3 = spInt1; //指针的引用计数加1,现为3 spInt3.reset(); //spInt3被重置,指针的引用计数减1,现为2
assert(!spInt3); return ; //spInt2退出作用域,指针引用计数减1,为1, spInt1退出作用域,指针引用计数减1,为0,此时释放内存
}

使用示例2:

class Foo;
typedef std::shared_ptr<Foo> SPtr; class Foo
{
}; void func(SPtr sp)
{
int cnt = sp.use_count();
cout << cnt << endl;
} int main()
{
func(SPtr(new Foo)); //输出为1 SPtr sp(new Foo);
func(sp); //输出为2 return ;
}

②、注意事项

shared_ptr不支持直接使用原始指针进行"="操作:

    int*p = new int();
std::shared_ptr<int> sp(p);
//std::shared_ptr<int> sp = p; //error !
//sp = p; //error !

对shared_ptr进行 = 操作后原shared_ptr的引用计数会减1,如果变为0的话就会释放其指向的对象,但原对象的释放是在新对象生成之后,稳妥的方法是=操作之前先进行reset操作:

    auto sp = std::make_shared<CFoo>();

    sp.reset(); //先使用reset()或= nullptr来释放原对象
sp = std::make_shared<CFoo>();

shared_ptr不能多次引用同一原始指针,否则会产生多次释放错误:

    CFoo* p = new CFoo;
std::shared_ptr<CFoo> sp(p);
std::shared_ptr<CFoo> sp2(p); //error !

shared_ptr不能直接包含对象的this指针,如下所示的代码同样会导致两次释放问题:

class Foo
{
public:
virtual ~Foo()
{
int a = ;
}
std::shared_ptr<Foo> getSharedPtr()
{
return std::shared_ptr<Foo>(this);
}
}; int main()
{
std::shared_ptr<Foo> sp(new Foo);
std::shared_ptr<Foo> spT = sp->getSharedPtr(); return ;
}

应该使用shared_from_this,需要注意的两点:shared_from_this()方法不能在当前类的构造函数中调用,如果当前对象由一个unique_ptr控制生命,那么对象内不能使用shared_from_this:

class Foo : public std::enable_shared_from_this<Foo>
{
public:
virtual ~Foo()
{
int a = ;
}
std::shared_ptr<Foo> getSharedPtr()
{
return shared_from_this();
}
}; int main()
{
std::shared_ptr<Foo> sp(new Foo);
std::shared_ptr<Foo> spT = sp->getSharedPtr(); return ;
}

shared_ptr不是线程安全的,它的的线程安全级别和内置类型、容器、string 一样,即:

l 多个线程对不同的shared_ptr写入是线程安全的,即使这些shared_ptr指向同一原始指针。
   l 同一个 shared_ptr 可被多个线程同时读取。
   l 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

如下所示:

shared_ptr<Foo> globalPtr;
Mutex mutex; void read()
{
shared_ptr<Foo> ptr;
{
MutexLock lock(mutex); //RAII Mutex
ptr = globalPtr; // read globalPtr
} // use ptr since here
doit(ptr);
} void write()
{
shared_ptr<Foo> newptr(new Foo);
{
MutexLock lock(mutex); //RAII Mutex
globalPtr = newptr; // write to globalPtr
} // use newptr since here
doit(newptr);
}

应该避免使用临时的shared_ptr对象,对此,boost上的说明是:假设有下面的代码,ok函数是正确的做法,而bad函数有可能会导致内存泄露,因为f函数参数的执行顺序可能是先new int(2),再执行g(),然后再执行shared_ptr<int>(),假设在g()方法中产生了异常,将不会再执行shared_ptr<int>(),new int(2)的内存泄露。

void f(shared_ptr<int>, int);
int g(); void ok()
{
shared_ptr<int> p(new int());
f(p, g());
} void bad()
{
f(shared_ptr<int>(new int()), g());
}

③、工厂函数make_shared

当shared_ptr的构造参数是一个new操作符的时候,虽然我们不用手动调用delete来释放它,可这导致了代码中的某种不对称性,所以应该使用工厂模式来解决:头文件"boost/make_shared.hpp"中提供了一个*工厂函数make_shared<T>()来消除显示的new操作,它可以返回一个shared_ptr<T>对象,使用示例:

#include "boost/smart_ptr.hpp"
#include "boost/make_shared.hpp"
#include <vector> int main()
{
boost::shared_ptr<string> sp = boost::make_shared<string>("make_shared");
boost::shared_ptr<std::vector<int>> spv = boost::make_shared<std::vector<int>>(, );
assert(spv->size() == ); return ;
}

C++11中也提供了std::make_shared<T>()来使用。

④、删除器

shared_ptr默认使用delete释放它指向的对象,我们可以通过使用其另一种构造方法来指定其他的释放操作。

shared_ptr有一种特殊形式的构造函数:shared_ptr(T* p, D d); 这里边的d就是删除器,它可以是一个函数对象或函数指针。删除器用来指定shared_ptr在析构的时候即离开作用域的时候不是执行释放内存的操作,而是执行d函数。函数get_deleter()可以获得删除器指针。

删除器使用示例1:

std::unique_ptr<int> u5 (new int, std::default_delete<int>());

删除器使用示例2:

class CSock
{
...
}; CSock* open_sock()
{
CSock* s = new CSock;
...//do some open job return p;
} void close_sock(CSock* s)
{
...//do some close job
delete s;
} boost::shared_ptr(CSock*)(open_sock(), close_sock);

删除器使用示例3:

struct Foo {
Foo() { std::cout << "Foo...\n"; }
~Foo() { std::cout << "~Foo...\n"; }
}; struct D {
void operator()(Foo* p) const {
std::cout << "Call delete from function object...\n";
delete p;
}
}; std::shared_ptr<Foo> sh4(new Foo, D()); std::shared_ptr<Foo> sh5(new Foo, [](auto p) {
std::cout << "Call delete from lambda...\n";
delete p;
});

删除器使用示例4:

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

⑤、shared_array

使用shared_array来指向使用new[]开辟的数组,它同样使用引用计数机制。

⑥、weak_ptr

我们一般使用一个shared_ptr或weak_ptr来初始化weak_ptr,当使用一个shared_ptr对象来构造weak_ptr的时候不会引起shared_ptr引用计数的增加,即weak_ptr不能控制对象的生命期。

weak_ptr主要用来获悉对象是存在的还是已经被释放了,通过成员函数lock()。lock()可以从被观测的shared_ptr获得一个shared_ptr对象,如果对象存在的话,lock()会导致shared_ptr的引用计数加1,如果对象已经被释放了则lock()返回空的shared_ptr(以默认构造函数构造的shared_ptr,其use_count()为0)。lock()是线程安全的。weak_ptr的expired()方法可以用来判断当前weak_ptr对象是否是空的,当weak_ptr没有观测对象(没有用shared_ptr进行初始化)或者观测对象已经被释放expired()返回true。weak_ptr也有use_count()方法,它获得的是被观测对象(shared_ptr)的引用计数。

    boost::weak_ptr<int> wpEmpty;
bool b = wpEmpty.expired(); //true boost::shared_ptr<int> sp(new int());
boost::weak_ptr<int> wp(sp);
b = wp.expired(); //false boost::shared_ptr<int> sp2 = wp.lock();
if (sp2)
{
assert(wp.use_count() == );
} sp.reset();
sp2.reset();
b = wp.expired(); //true

当两个shared_ptr互相包含的时候,会导致双方无法释放的问题,比如下面这种情况,当func()返回的时候两个对象都得不到释放,解决方法是使用weak_ptr来代替shared_ptr。

class Child;
class Parent
{
public:
~Parent() { cout << "Parent destruct" << endl; }
shared_ptr<Child> m_spChild;
}; class Child
{
public:
~Child() { cout << "Child destruct" << endl; }
shared_ptr<Parent> m_spParent;
}; void Func()
{
shared_ptr<Parent> spParent(new Parent);
shared_ptr<Child> spChild(new Child);
spParent->m_spChild = spChild;
spChild->m_spParent = spParent;
} int main()
{
Func(); getchar();
return ;
}

 3、unique_ptr

unique_ptr与shared_ptr不同的是只能有一个unique_ptr指向一个对象,因此unique_ptr不支持普通的拷贝或赋值操作(使用工厂函数make_unique<>进行 = 初始化除外):

    std::unique_ptr<string> p1(new string("test"));
std::unique_ptr<string> p2 = std::make_unique<string>("hello");
p2 = std::make_unique<string>("abc"); p2 = p1; //error
std::unique_ptr<string> p3(p1); //error
std::unique_ptr<string> p4 = p1; //error

成员方法get()可以获得当前管理对象的指针,release()或reset()将指针的所有权从一个unique_ptr转移到另一个unique_ptr。release()会返回当前保存的指针,而且它仅仅是切断与当前管理对象的关系,并不会释放该对象资源。reset()接收一个新的指针,并将原来管理的对象释放。不能拷贝unique_ptr的规则有一个例外,我们可以拷贝一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr:

std::unique_ptr<string> sp1;
sp1.reset(new string); //使用reset初始化 std::unique_ptr<string> sp2;
sp2 = std::unique_ptr<string>(new string); //使用临时的unique_ptr赋值 std::unique_ptr<int> clone(int num)
{
//返回临时unique_ptr
return std::unique_ptr<int>(new int(num));
} std::unique_ptr<int> clone(int num)
{
std::unique_ptr<int> ret(new int(num));
return ret; //返回局部unique_ptr
}

类似shared_ptr,unique_ptr也可以指定删除器,但它与管理删除器的方式与shared_ptr有所不同。