文章目录
- 1.boost智能指针
- 2.scoped_ptr
- 3.shared_ptr
- 4.weak_ptr
- 5.scoped_array/shared_array
- 6.PIMPL技法
1.boost智能指针
-
智能指针是利用RAII(Resource Acquisition Is Initialization:资源获取即初始化)来管理资源
在构造函数中对资源初始化,在析构函数中对资源释放 -
智能指针的本质思想是:
(1)将堆对象的生存期用栈对象(智能指针)来管理,当new一个堆对象的时候,立刻用智能指针来接管。具体做法是:在构造函数进行初始化(用一个指针指向堆对象),在析构函数中调用delete来释放堆对象。
(2)由于智能指针本身是一个栈对象,他的作用域结束的时候,会自动调用析构函数,从而调用了delete释放了堆对象 -
常用的智能指针
scoped_ptr对象既不能被拷贝,也不能被赋值
intrusive_ptr不常用
waek_ptr主要是解决循环引用的问题
-
boost库在vs2008的配置,下载boost库后
2.scoped_ptr
- 智能指针本身是栈上对象
- eg:P87\01.cpp
#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;class X
{
public:X(){cout << "X ..." << endl;}~X(){cout << "~X ..." << endl;}
};int main( void)
{cout << "Entering main ..." << endl;{//X堆对象,由智能指针栈对象pp来管理//智能指针栈对象pp销毁的时候,他所管理的堆对象也就跟着销毁了boost::scoped_ptr<X> pp( new X);//boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移}cout << "Exiting main ..." << endl;return 0;
}
-
测试:
-
断点:
boost::scoped_ptr<X> pp( new X);
构造函数:px指针接管
析构函数:
typedef那两行是判定T是否是完整类型
//boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移
拷贝构造和=号运算符都是私有的
3.shared_ptr
- 内部维护了一个引用计数reference
- eg:P87\02.cpp
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;class X
{
public:X(){cout << "X ..." << endl;}~X(){cout << "~X ..." << endl;}
};int main( void)
{cout << "Entering main ..." << endl;boost::shared_ptr<X> p1( new X);cout << p1.use_count() << endl;//输出引用计数boost::shared_ptr<X> p2 = p1;//用p1对象初始化p2对象,调用拷贝构造函数,相当于共享一个对象//boost::shared_ptr<X> p3;//p3 = p1;cout << p2.use_count() << endl;//其值等于p1.use_count()p1.reset();//表示置空,显式的将引用计数-1,也可以不用,等程序结束的时候会进行的,因为是智能指针是栈对象嘛cout << p2.use_count() << endl;p2.reset();cout << "Exiting main ..." << endl;return 0;
}
- 测试:
- 断点:
boost::shared_ptr<X> p1( new X);
接着调用class X的构造函数,略;
接着调用shared_ptr的构造函数,px是一个指针,pn是管理引用计数的
要调用构造函数,对象成员的构造函数要先调用,会调用shared_count的构造函数
F11
F11,调用其构造函数
F11,
会调用基类的构造函数,强引用use_count_,弱引用weak_count_初始化为1
- 断点:
cout << p1.use_count() << endl;//输出引用计数
F11
- 断点位置如下:直接在boost库中打断点
目的是看boost::shared_ptr<X> p2 = p1;调用默认拷贝构造函数后,引用计数怎么到这里++的
引用计数+1的操作位置,这是原子性的自增操作,shared_ptr是线程安全的,内部的引用计数是线程安全的
shared_ptr没有提供拷贝构造函数,是编译器提供的默认拷贝构造函数
双击是看不到默认的拷贝构造函数
从上往下看,还看到还会调用shared_count的拷贝构造函数,因为shared_count是其内部所持有的对象
注意:shared_count const & r这里的r是一个常量,修改常量的值这里有个技巧:
pi_和r.pi指针指向同一个地方,现在堆pi_进行修改,对pi_所做的修改就是对r.pi所做的修改,这样就成功的将常量r进行了修改
const 常量
const int n = 100;
p = &n;
*p = 300;是可以修改常量的值的
- eg:P87\03.cpp
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;class X
{
public:X(){cout << "X ..." << endl;}~X(){cout << "~X ..." << endl;}
};int main( void)
{cout << "Entering main ..." << endl;boost::shared_ptr<X> p1( new X);cout << p1.use_count() << endl;//输出引用计数boost::shared_ptr<X> p2 = p1;//用p1对象初始化p2对象,调用拷贝构造函数,相当于共享一个对象boost::shared_ptr<X> p3;p3 = p1;//等号运算符cout << p2.use_count() << endl;//其值等于p1.use_count()p1.reset();//表示置空,显式的将引用计数-1,也可以不用,等程序结束的时候会进行的,因为是智能指针是栈对象嘛cout << p2.use_count() << endl;p2.reset();cout << "Exiting main ..." << endl;return 0;
}
- 测试:
- 断点:
p3 = p1;
this_type会调用拷贝构造函数,并把它交换给this;即=运算符也是借助拷贝构造使得引用计数+1的;
- auto_ptr不能放置在vector中,但是shared_ptr是可以放置在vector中的。
- eg:P87\04.cpp
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <memory>
#include <vector>
using namespace std;class X
{
public:X(){cout << "X ..." << endl;}~X(){cout << "~X ..." << endl;}
};int main( void)
{//编译失败// vector<auto_ptr<X> > v;// auto_ptr<X> p(new X);// v.push_back(p);//boost::shared_ptr<X>是可以放到vector中的vector<boost::shared_ptr<X> > v;boost::shared_ptr<X> p(new X);v.push_back(p);//push_back内部会构造一个shared_ptr对象,与p一样,所以输出为2cout<<v.use_count<<endl;//2个对象都引用了X//当p对象销毁,向量v中的对象也被销毁的时候,引用计数减为0,则堆对象X自动被销毁return 0;
}
- 测试:
编译失败
push_back时会调用=号运算符
auto_ptr不能放置在vector中的原因是:
auto_ptr的=号运算符如下:这里要求是非const的的引用,在做=运算时,_Right.release()所有权要发生转移,_Right变量的内容会发生改变
而vector里面的=号运算符,_Val是一个常量,是不能被更改的,即上面的_Right.release()是不能被更改的,不符合上面的auto_ptr<_Other>& _Right这里的接口,所以编译出错。
编译成功的原因:接口是const的,符合接口。const变量不代表不能被更改,shared_ptr内部通过指针的方式来修改常量,但是接口保留const。
所以,auto_ptr基本都不用了。
- shared_ptr注意事项
详见:http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm
//实参传递到形参scoped_ptr<T>控制权不能转移,不能像下面这么用,根本就是错的
void f(shared_ptr<int>, int);
int g();void ok()
{//这是没有内存泄漏的//f(p, g());会调用拷贝构造函数,形参与p共享对象,他俩都结束时,引用计数减为0//new int(2)对象会销毁shared_ptr<int> p(new int(2));f(p, g());
}void bad()
{//可能存在内存泄漏//如果new int(2)构造,接着g()构造,此时在g()中抛出异常//没有调用shared_ptr的构造函数//那么new int(2)就没有被shared_ptr所接管,会造成内存泄漏//如果编译器的执行顺序是:从右往左,那么就不会,所以也要看编译器f(shared_ptr<int>(new int(2)), g())
}
4.weak_ptr
-
循环引用问题
-
eg:P87\05.cpp
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;class Parent;
class Child;
typedef boost::shared_ptr<Parent> parent_ptr;
typedef boost::shared_ptr<Child> child_ptr;class Child
{
public:Child(){cout << "Child ..." << endl;}~Child(){cout << "~Child ..." << endl;}parent_ptr parent_;//持有一个Parent类的智能指针对象
};class Parent
{
public:Parent(){cout << "Parent ..." << endl;}~Parent(){cout << "~Parent ..." << endl;}child_ptr child_;//持有一个Child的智能指针对象
};int main( void)
{parent_ptr parent( new Parent);child_ptr child( new Child);//Parent父亲对象中的孩子指向孩子对象parent->child_ = child;//孩子对象引用计数=2//Child孩子对象中的父亲指向父亲对象child->parent_ = parent;//父亲对象引用计数=2//这样父亲对象和孩子对象都无法正常释放return 0;
}
-
测试:析构函数都没调用
-
解决办法1:手动打破循环引用(不好,还不如用原生指针)
-
eg:P87\06.cpp
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;class Parent;
class Child;
typedef boost::shared_ptr<Parent> parent_ptr;
typedef boost::shared_ptr<Child> child_ptr;class Child
{
public:Child(){cout << "Child ..." << endl;}~Child(){cout << "~Child ..." << endl;}parent_ptr parent_;//持有一个Parent类的智能指针对象
};class Parent
{
public:Parent(){cout << "Parent ..." << endl;}~Parent(){cout << "~Parent ..." << endl;}child_ptr child_;//持有一个Child的智能指针对象
};int main( void)
{parent_ptr parent( new Parent);child_ptr child( new Child);//Parent父亲对象中的孩子指向孩子对象parent->child_ = child;//孩子对象引用计数=2//Child孩子对象中的父亲指向父亲对象child->parent_ = parent;//父亲对象引用计数=2//这样父亲对象和孩子对象都无法正常释放parent->child_.reset();//让孩子对象的引用计数变成1,打破循环引用//child智能指针对象销毁时,当前引用计数从1->0,所以Child对象被销毁,且Child对象//又持有parent_,此时parent_引用计数减为1,当Parent对象销毁时,parent_计数从1->0,所以///Parent会被销毁return 0;
}
-
测试:
-
解决办法2:使用weak_ptr解决(自动非人工)
(1)强引用,只要有一个引用存在,对象就不能释放
(2)弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在
如果存在,提升为shared_ptr(强引用)成功;
如果不存在,提升失败;
(3)通过weak_ptr访问对象的成员的时候,要提升为shared_ptr
(4)shared_ptr是强引用
weak_ptr是弱引用,即使对象销毁了,可能弱引用还存在,此时继续提升,可能会失败。 -
eg:
-
测试:
-
eg:P87\08.cpp
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;class X
{
public:X(){cout << "X ..." << endl;}~X(){cout << "~X ..." << endl;}void Fun(){cout << "Fun ..." << endl;}
};
int main( void)
{//weak_ptr通常要与shared_ptr配合使用boost::weak_ptr<X> p;//定义一个若指针对象引用{boost::shared_ptr<X> p2( new X);cout << p2.use_count() << endl;//=1p = p2;cout << p2.use_count() << endl;//p2是强引用,引用计数=1//要访问X中的数据成员,首先要将弱引用p提升为shared_ptr//弱引用没有重载->指针运算符,强引用有的boost::shared_ptr<X> p3 = p.lock();if (!p3)cout<<"object is destroyed"<<endl;//p.lock()看下能否提升,lock()表示提升,也表示锁定对象,防止被销毁elsep3->Fun();}//p2引用的对象会被销毁,因为引用计数=1boost::shared_ptr<X> p4 = p.lock();if (!p4)cout<<"object is destroyed"<<endl;elsep4->Fun();return 0;
}
- 测试:
5.scoped_array/shared_array
- 针对于数组
- eg:P87\10.cpp
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;class X
{
public:X(){cout << "X ..." << endl;}~X(){cout << "~X ..." << endl;}void Fun(){cout << "Fun ..." << endl;}
};
int main( void)
{//weak_ptr通常要与shared_ptr配合使用boost::weak_ptr<X> p;//定义一个若指针对象引用{boost::shared_ptr<X> p2( new X);cout << p2.use_count() << endl;//=1p = p2;cout << p2.use_count() << endl;//p2是强引用,引用计数=1//要访问X中的数据成员,首先要将弱引用p提升为shared_ptr//弱引用没有重载->指针运算符,强引用有的boost::shared_ptr<X> p3 = p.lock();if (!p3)cout<<"object is destroyed"<<endl;//p.lock()看下能否提升,lock()表示提升,也表示锁定对象,防止被销毁elsep3->Fun();}//p2引用的对象会被销毁,因为引用计数=1boost::shared_ptr<X> p4 = p.lock();if (!p4)cout<<"object is destroyed"<<endl;elsep4->Fun();boost::scoped_array<X> xx(new X[3]);// boost::scoped_ptr<X> xx(new X[3]);//error,析构不带[]return 0;
}
- 测试:
析构函数带[]
6.PIMPL技法
- 引入更多的头文件,降低编译速度
y.cpp和main.cpp都引入了x.h - 提高模块的耦合度
编译期:Y类依赖X的实现,X类改变了,Y需要重新编译
运行期:若X类有子类,子类若有Fun(),则无法实现替换,无法使用多态 - 降低了接口的稳定程度
(1)对于库的使用,方法不能改变
(2)对于库的编译,动态库的变更,客户程序不用重新编译
若没有下面的动态库和客户程序的区分,那么如果X改变,则会影响Y,就会影响main.cpp,则所有程序都需要重新编译 - 不使用PIMPL的eg
//假设下面是动态库start
//file y.h
#include "x.h"
class Y
{
public:void Func_Y();X x_;
};//file y.cpp
#include "y.h"
void Y::Fun()
{return x_.Fun_X();
}
//end//假设下面是客户程序
//file main.cpp
#include "y.h"
int main(void)
{Y y;y.Fun();
}
- PIMPL技法
- PIMPL(private implementation或pointer to implementation)也称之为handle/body idiom
将实现隐藏,或者将指针指向一个实现,或者指针称之为handle,指针指向的实现称之为body(指针指向惯用法) - PIML背后的思想是把客户与所有关于类的私有部分隔离开。避免其他类知道其内部结构
具体做法就是用一个指针来解决,因为类X发生了改变,指针大小是不变的,所以Y类是不用编译的,可以降低编译量 - 降低编译依赖,提高重编译速度
- 接口和实现分离
- 降低模块的耦合度
编译器
运行期 - 提高了接口的稳定程度
(1)对于库的使用,方法不能改变
(2)对于库的编译,动态库的变更,客户程序不用编译
假设X发生改变,意味着库要进行变更,但是客户程序不需要编译,因为Y与其内部的实现细节是分离的,只要Y的接口保持不变。 - PIMPL的eg:P87\12.cpp
//假设下面是动态库start
//y.h不需要包含x.h,只需要前向声明,因为指针的原因X*
//file y.h
class Y
{Y();~Y();void Fun();X* px_;//即使类X发生变化,Y类不需要编译,指针所指向的类到底是怎么实现的,分离了//因为指针总是4个字节或者8个字节//在运行期,还可以应用多态,因为这是指针//也可以用智能指针,因为智能指针所持有的对象类型发生了改变,但智能指针的大小是不会改变的//智能指针还能自动管理px_的生存期
};//file y.cpp
#include "x.h"
Y::Y() : px_(new X) {}
Y::~Y() {delete px_; px_ = 0;}
void Y::Fun() {return px_->Fun();}
//end//假设下面是客户程序
//file main.cpp
#include "y.h"//这里y.h没有包含x.h
int main(void)
{Y y;y.Fun();
}
- eg:pimpl的又一个eg
//类Y依赖下面的三个类,相当于要依赖他们具体的实现了
class Y
{A a;B b;C c;
};可以将他们提升到一个类中
class Y
{Impl* p;
};
class Impl
{A a;B b;C c;
};
- 参考:从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr 、shared_ptr 、weak_ptr 源码分析)