文章目录

  • ​​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主要是解决循环引用的问题
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言

  • boost库在vs2008的配置,下载boost库后
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_后端_02

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;
}
  • 测试:
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_03

  • 断点:
boost::scoped_ptr<X> pp( new X);

构造函数:px指针接管

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_04


析构函数:

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_05


typedef那两行是判定T是否是完整类型

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_06

//boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移

拷贝构造和=号运算符都是私有的

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_07

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);

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_08


接着调用class X的构造函数,略;

接着调用shared_ptr的构造函数,px是一个指针,pn是管理引用计数的

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_09


(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_10


要调用构造函数,对象成员的构造函数要先调用,会调用shared_count的构造函数

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_11


(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_后端_12


F11

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_后端_13


F11,调用其构造函数

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_14


F11,

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_15


会调用基类的构造函数,强引用use_count_,弱引用weak_count_初始化为1

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_16

  • 断点:
cout << p1.use_count() << endl;//输出引用计数

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_#include_17


F11

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_后端_18

  • 断点位置如下:直接在boost库中打断点
目的是看boost::shared_ptr<X> p2 = p1;调用默认拷贝构造函数后,引用计数怎么到这里++的

引用计数+1的操作位置,这是原子性的自增操作,shared_ptr是线程安全的,内部的引用计数是线程安全的

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_#include_19


shared_ptr没有提供拷贝构造函数,是编译器提供的默认拷贝构造函数

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_20


双击是看不到默认的拷贝构造函数

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_21


从上往下看,还看到还会调用shared_count的拷贝构造函数,因为shared_count是其内部所持有的对象

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_22


注意:shared_count const & r这里的r是一个常量,修改常量的值这里有个技巧:

pi_和r.pi指针指向同一个地方,现在堆pi_进行修改,对pi_所做的修改就是对r.pi所做的修改,这样就成功的将常量r进行了修改

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_23

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的;

(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_c语言_24


(P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_25

  • 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一样,所以输出为2
cout<<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

  • 循环引用问题
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_26

  • 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;
}
  • 测试:析构函数都没调用
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_27

  • 解决办法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;
}
  • 测试:
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_引用计数_28

  • 解决办法2:使用weak_ptr解决(自动非人工)
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_后端_29

  • (1)强引用,只要有一个引用存在,对象就不能释放
    (2)弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在
    如果存在,提升为shared_ptr(强引用)成功;
    如果不存在,提升失败;
    (3)通过weak_ptr访问对象的成员的时候,要提升为shared_ptr
    (4)shared_ptr是强引用
    weak_ptr是弱引用,即使对象销毁了,可能弱引用还存在,此时继续提升,可能会失败。
  • eg:
  • 测试:
  • (P87+P88)boost智能指针:boost智能指针,scoped_ptr ,shared_ptr,weak_ptr,scoped_array/shared_array,pimpl技法_开发语言_30

  • 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;//=1
p = 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()表示提升,也表示锁定对象,防止被销毁
else
p3->Fun();
}
//p2引用的对象会被销毁,因为引用计数=1

boost::shared_ptr<X> p4 = p.lock();
if (!p4)
cout<<"object is destroyed"<<endl;
else
p4->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;//=1
p = 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()表示提升,也表示锁定对象,防止被销毁
else
p3->Fun();
}
//p2引用的对象会被销毁,因为引用计数=1

boost::shared_ptr<X> p4 = p.lock();
if (!p4)
cout<<"object is destroyed"<<endl;
else
p4->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 源码分析)​​