智能指针的出现是为了解决:
C++没有内存回收机制,每次程序员new出来的对象需要手动delete,流程复杂时可能会漏掉delete,导致内存泄漏。于是C++引入智能指针,可用于动态资源管理,资源即对象的管理策略。使用 raw pointer 管理动态内存时,经常会遇到这样的问题:
忘记delete内存,造成内存泄露。
出现异常时,不会执行delete,造成内存泄露。
下面用一个小例子来说明其实现的原理。
#include
#include
struct C { int* data; };
int main()
{
std::shared_ptr p1;//默认构造函数,refcount为0
std::shared_ptr p2(nullptr);//使用一个空的对象初始化时refcount也为0
//普通指针初始化是引用计数为1,p3,p4
std::shared_ptr p3(new int);
std::shared_ptr p4(new int, std::default_delete());
//拥有allocator的时候初始化同样引用计数为1
//但是紧接着又用改智能指针拷贝构造初始化其他智能指针p6,
//所以最后其引用计数为2,p5
std::shared_ptr p5(new int, [](int* p) {delete p; }, std::allocator());
//这里p6本身为1,但是因为使用std::move去初始化p7,将p6指向转给了p7
//则p6智能指针recount--变为0,p7 ++由1变为2
std::shared_ptr p6(p5);
std::shared_ptr p7(std::move(p6));
std::shared_ptr p8(std::unique_ptr(new int));
std::shared_ptr obj(new C);
std::shared_ptr p9(obj, obj->data);
std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n';
std::cout << "p2: " << p2.use_count() << '\n';
std::cout << "p3: " << p3.use_count() << '\n';
std::cout << "p4: " << p4.use_count() << '\n';
std::cout << "p5: " << p5.use_count() << '\n';
std::cout << "p6: " << p6.use_count() << '\n';
std::cout << "p7: " << p7.use_count() << '\n';
std::cout << "p8: " << p8.use_count() << '\n';
std::cout << "p9: " << p9.use_count() << '\n';
return 0;
}
1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。
将shared_ptr作为容器的元素,如vector
#include
#include
#include
#include
#include
#include
#include
using namespace boost;
int main()
{
typedef std::vector> vs;
vs v(10);
int i = 0;
for (vs::iterator pos = v.begin(); pos != v.end(); ++pos)
{
(*pos) = make_shared(++i); //使用工厂函数赋值
using namespace std;
cout << *(*pos) << ","; //输出值
}
using namespace std;
cout << endl;
boost::shared_ptr p = v[9];
*p = 100;
cout << *v[9] << endl;
}
桥接模式(bridge)是一种结构型的设计模型,它把类的具体的实现细节对用户隐藏起来,已到达类之间的最小 耦合,类内高度内聚(高内聚低耦合),在具体的实现中桥接模式也被称为pimpl或者handle/body惯用法,它可以将 头文件的 依赖关系讲到最低,减少编译时间,而且可以比使用虚函数实现多态通过一个小例子来说明share_ptr如何用于桥接模式
class sample
{
private:
class impl; //不完整的内部类声明
shared_ptr p; //shared_ptr的成员变量
public:
sample(); //构造函数
void print(); //提供给外界的接口
};
//在sample中完整定义impl类和其他功能
class sample::impl
{
public:
void print()
{
using namespace std;
cout << "impl print" << endl;
}
};
sample::sample() :p(new impl) {} //构造函数初始化shared_ptr
void sample::print() //调用pimpl实现print()
{
p->print();
}
//最后是使用桥接模式
int main()
{
sample s;
s.print();
}
桥接模式非常有用,它可以任意改变具体的实现外而界对此一无所知,也减少了文件之间的编译依赖,是程序获得更多的灵活性
工厂模式是一种创建型设计模式,这个模式包装了new操作符的使用,使对象的创建工作集中在工厂类或者工厂函数中,从而更容易适应变化,make_shared()就是工厂模式的一个很好的例子
但由于C++不能高效的返回一个对象,在程序中编写自己的工厂类或者工厂函数中时通常需要早堆区使用new的方法动态的分配一个对象,然后返回指针。这种做法很不安全,因为用户很容易忘记对指针调用delete,存在资源泄露的隐患。所以用shared_ptr来解决这个问题
class abstract //接口类的定义
{
public:
virtual void f() = 0;
virtual void g() = 0;
protected:
virtual ~abstract() {}; //注意这里
};
/*注意abstract的析构函数,被定义为保护的,意味着除了它自己和他的子类,其他的对象无权来调用
delete来删除它。*/
class impl :public abstract
{
public:
virtual void f()
{
using namespace std;
cout << "class impl f" << endl;
}
virtual void g()
{
using namespace std;
cout << "class impl g" << endl;
}
};
//随后工厂函数返回基类的shared_ptr
shared_ptrcreat()
{
return shared_ptr(new impl);
}
//这样就完成全部的工厂模式的实现,现在可以把这些组合起来
int main()
{
shared_ptr p = creat();//工厂函数创建对象
p->f(); //可以像普通指针一样使用
p->g(); //不必担心资源泄露,shared_ptr会自动管理指针
}
shared_ptr
shared_array和shared_ptr的接口功能几乎时相同的,主要区别如下:
构造函数接受的P指针必修是new[]的结果,而不能是new表达式的结果
提供operator【】操作符重载,可以像普通数组一样用下标访问元素
没有*、->操作符的重载,因为shared_array持有的不是一个普通指针
析构函数用的是delete【】而不是delete。
shared_array就像是shared_ptr和scoped_array的结合体哟杨具有shared_ptr的优点,也有scoped_array的缺点;
#include
#include
#include
#include
#include
#include
#include
using namespace boost;
int main()
{
int *p = new int[100]; //一个动态数组
shared_array sa(p); //shared_array代理的动态数组
shared_array sa2 = sa;//共享数组,引用技术增加
sa[0] = 10; //可以使用operator访问元素
assert(sa2[0] == 10);
}
同样的,在使用shared_array重载的operator【】时要小心,shared_array不提供数组索引的范围检查,如果使用了超过动态数组大小的索引或者时负数索引将引发可怕的未定义行为;一般情况下使用shared_ptr
后续继续更新相关内容