彻底搞清c/c++中的几个指针概念:悬垂指针和智能指针以及哑指针和野指针

来源:互联网 发布:常见的数据库类型三种 编辑:程序博客网 时间:2024/04/29 10:19

分类: 技术资料 469人阅读 评论(0) 收藏 举报
deletetuplesreferenceclass工作fun

先看看下面两段代码运行结果:

#include<iostream>
//#include <windows.h>
using namespace std;
int *p=NULL;
void fun()
{int i=10;p=&i;}
void main()
{
//fun();
 int i=10;p=&i;
//cout<<"*p= "<<*p<<endl;
//Sleep(1000);
//cout<<"一秒钟后,fun()中的i变量的存储空间被释放,p所指对象的值为:"<<endl<<"*p= "<<*p<<endl;
cout<<"*p= "<<*p<<endl;
cout<<"*p= "<<*p<<endl;
}

结果:

*p=10;

*p=10;

 

#include<iostream>
//#include <windows.h>
using namespace std;
int *p=NULL;
void fun()
{int i=10;p=&i;}
void main()
{
fun();
 //int i=10;p=&i;
//cout<<"*p= "<<*p<<endl;
//Sleep(1000);
//cout<<"一秒钟后,fun()中的i变量的存储空间被释放,p所指对象的值为:"<<endl<<"*p= "<<*p<<endl;
cout<<"*p= "<<*p<<endl;
cout<<"*p= "<<*p<<endl;
}

结果:

*p=10;

*p=13454657;

原因

一秒钟后,fun()中的i变量的存储空间被释放,p所指对象的值为:

  *p= 13454657

这就是悬垂指针

定义:指向曾经存在的对象,但该对象已经不再存在了,此类指针称为垂悬指针。结果未定义,往往导致程序错误,而且难以检测。

避免方法:

引入智能指针可以防止垂悬指针出现。一般是把指针封装到一个称之为智能指针类中,这个类中另外还封装了一个使用计数器,对指针的复制等操作将导致该计数器的值加1,对指针的delete操作则会减1,值为0时,指针为NULL

 哑指针:

1.哑指针指传统的C/C++指针,它只是一个指向,除此以外它不会有其他任何动作,所有的细节必须程序员来处理,比如指针初始化,释放等等

智能指针:

当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。

是指一种实现,能让指针在离开自己生命周期的时候自动销毁指向的内容(对象等),这往往用一个对象将指针包装起来来实现,例如标准库中的auto_ptr和boost中的智能指针都是智能指针的例子,但是缺点就是没有带引用参数。


  智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。智能指针结合了栈的安全性和堆的灵活性,本质上将就是栈对象内部包装一个堆对象

  每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。

  实现引用计数有两种经典策略:一是引入辅助类,二是使用句柄类。下面分别介绍这些内容

  问题描述  

  假设有一个名为TestPtr的类,里面有一个指针成员,简化为如下代码

  class TestPtr

  {

  public:

  TestPtr(int *p): ptr(p) { }

  ~TestPtr( ) { delete ptr; }

  // other operations

  private:

  int *ptr;

  // other data

  };

  在这种情况下,类TestPtr对象的任何拷贝、赋值操作都会使多个TestPtr对象共享相同的指针。但在一个对象发生析构时,指针指向的对象将被释放,从而可能引起悬垂指针。

  现在我们使用引用计数来解决这个问题,一个新的问题是引用计数放在哪里。显然,不能放在TestPtr类中,因为多个对象共享指针时无法同步更新引用计数。

  方案一

  这里给出的解决方案是,定义一个单独的具体类(RefPtr)来封装指针和相应的引用计数。由于这个类只是用于对类TestPtr中的成员指针ptr进行了封装,无其它用途,所以把引用计数类RefPtr的所有成员均定义为private,并把类TestPtr声明为它的友元类,使TestPtr类可以访问RefPtr类。示例代码如下:

  class RefPtr

  {

  friend class TestPtr;

  int *ptr;

  size_t count;

  RefPtr (int *p): ptr(p), count(1) {}

  ~RefPtr () {

  delete ptr;

  }

  };

  class TestPtr

  {

  public:

  TestPtr(int *p): ptr(new RefPtr(p)) { }

  TestPtr(const TestPtr& src): ptr(src.ptr) {

  ++ptr->count;

  }

  TestPtr& operator= (const TestPtr& rhs) {

  // self-assigning is also right

  ++rhs.ptr->count;

  if (--ptr->count == 0)

  delete ptr;

  ptr = rhs.ptr;

  return *this;

  }

  ~TestPtr() {

  if (--ptr->count == 0)

  delete ptr;

  }

  private:

  RefPtr *ptr;

  };

  当希望每个TestPtr对象中的指针所指向的内容改变而不影响其它对象的指针所指向的内容时,可以在发生修改时,创建新的对象,并修改相应的引用计数。这种技术的一个实例就是写时拷贝(Copy-On-Write)。

  这种方案的缺点是每个含有指针的类的实现代码中都要自己控制引用计数,比较繁琐。特别是当有多个这类指针时,维护引用计数比较困难。

  方案二

  为了避免上面方案中每个使用指针的类自己去控制引用计数,可以用一个类把指针封装起来。封装好后,这个类对象可以出现在用户类使用指针的任何地方,表现为一个指针的行为。我们可以像指针一样使用它,而不用担心普通成员指针所带来的问题,我们把这样的类叫句柄类。在封装句柄类时,需要申请一个动态分配的引用计数空间,指针与引用计数分开存储。实现示例如下

  #include <iostream>

  #include <stdexcept>

  using namespace std;

  #define TEST_SMARTPTR

  class Stub

  {

  public:

  void print() {

  cout<<"Stub: print"<<endl;

  }

  ~Stub(){

  cout<<"Stub: Destructor"<<endl;

  }

  };

  template <typename T>

  class SmartPtr

  {

  public:

  SmartPtr(T *p = 0): ptr(p), pUse(new size_t(1)) { }

  SmartPtr(const SmartPtr& src): ptr(src.ptr), pUse(src.pUse) {

  ++*pUse;

  }

  SmartPtr& operator= (const SmartPtr& rhs) {

  // self-assigning is also right

  ++*rhs.pUse;

  decrUse();

  ptr = rhs.ptr;

  pUse = rhs.pUse;

  return *this;

  }

  T *operator->() {

  if (ptr)

  return ptr;

  throw std::runtime_error("access through NULL pointer");

  }

  const T *operator->() const {

  if (ptr)

  return ptr;

  throw std::runtime_error("access through NULL pointer");

  }

  T &operator*() {

  if (ptr)

  return *ptr;

  throw std::runtime_error("dereference of NULL pointer");

  }

  const T &operator*() const {

  if (ptr)

  return *ptr;

  throw std::runtime_error("dereference of NULL pointer");

  }

  ~SmartPtr() {

  decrUse();

  #ifdef TEST_SMARTPTR

  std::cout<<"SmartPtr: Destructor"<<std::endl; // for testing

  #endif

  }

  private:

  void decrUse() {

  if (--*pUse == 0) {

  delete ptr;

  delete pUse;

  }

  }

  T *ptr;

  size_t *pUse;

  };

  int main()

  {

  try {

  SmartPtr<Stub> t;

  t->print();

  } catch (const exception& err) {

  cout<<err.what()<<endl;

  }

  SmartPtr<Stub> t1(new Stub);

  SmartPtr<Stub> t2(t1);

  SmartPtr<Stub> t3(new Stub);

  t3 = t2;

  t1->print();

  (*t3).print();

  return 0;

  }

STL中auto_ptr的实现:

  Stl  中 auto_ptr只是众多可能的智能指针之一,auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。
    这里是一个简单的代码示例,如果没有auto_ptr,
    
   

 

 1void ProcessAdoption(istream &data)
 2{
 3 
 4    while (data)                            // 如果还有数据
 5    {
 6        ALA   *pa = readALAData(data);      // 取出下一个数据
 7        pa->DealProcessAdoption(data);        // 处理
 8 
 9        delete pa;                          // 释放资源
10    }

11    return;
12}


     如果在DealProcessAdoption有一个exception,会发生什么事情,因为ProcessAdoption不能捕获他,所以这段代码很危险,所以DealProcessAdoption后面的代码可能会跳过,造成内存泄露。
如果利用try catch去捕获他,会搞得代码很乱,又缺少美观性。

改成下面的也是一种解决方案:

void ProcessAdoption(istream &data)
 2{
 3 try{
 4    while (data)                            // 如果还有数据
 5    {
 6        ALA   *pa = readALAData(data);      // 取出下一个数据
 7        pa->DealProcessAdoption(data);        // 处理
 8 
 9        delete pa;                          // 释放资源
10    }

           }catch(...)

          {

           cerr<<"exception"<<endl;

           delete pa;

            }

11    return;
12}


所以Stl提供了一个智能指针来解决这个问题,我们可以先模拟实现一个智能指针的类实现。

 

 1// 关于一个智能指针的定义
 2template<typename Type>
 3class auto_ptr
 4{
 5public:
 6    auto_ptr(T *=NULL) :Ptr(p)
 7    {     }
 8    ~auto_ptr()
 9    {
10        delete Ptr;
11    }

12private:
13    Type *Ptr;
14}
;
15
16
17void ProcessAdoption(istream &data)
18{
19
20    while (data)                            // 如果还有数据
21    {
22        auto_ptr<ALA> pa(readALADara(data));
23        pa->DealProcessAdoption(data);
24    }

25    return;
26}


这个版本和原先版本的差异只有二处,
第一pa是一智能指针的对象,不是ALA*
第二不用自己去释放delete

然后我看到Effective STL的条款
8:永不建立auto_ptr的容器
关于此可以看的Effective STL的条款8

因为auto_ptr并不是完美无缺的,它的确很方便,但也有缺陷,在使用时要注意避免。首先,不要将auto_ptr对象作为STL容器的元素。C++标准明确禁止这样做,否则可能会碰到不可预见的结果


注意auto_ptr也有缺陷,本质是它不是带引用计数的智能指针,它有一个怪癖:就是2个auto_ptr对象a1和a2不能同时拥有同一个元素,即使作为函数的参数实参传递,因为如果重新赋值的话,先前的auto_ptr对象将失去对该元素的所有权,就会被设置为NULL 指针,试想当要释放先前已经置为NULL的auto_ptr对象的内存,就会发生程序当掉。

#include<memory>
#include<iostream>
using namespace std;
class Nothing
{
public:
    Nothing(){cout<<"Nothing()"<<endl;}
    ~Nothing(){cout<<"~Nothing()"<<endl;}

};
void funn(Nothing *paa)
{cout<<"funn()11"<<endl;
auto_ptr<Nothing> a2 (paa);
cout<<"funn()"<<endl;
}
void v()
{

cout<<"v()"<<endl;
}
int main () {
   

    Nothing * pa=new Nothing();
    auto_ptr<Nothing> a1 (pa);
    
    funn(pa);
    pa=new Nothing();//如果没有这句程序会当掉 ,因为auto_ptr两个对象a1和a2拥有同一个元素,使得元素的所有权重置了,而a1对象置为NULL,只用重新赋值了。
    //auto_ptr<Nothing> a2 (pa);
    //delete pa;
  return 0;
}




auto_ptr的另一个缺陷是将数组作为auto_ptr的参数: auto_ptr<char>  pstr (new char[12] ); //数组;为定义

//auto_ptr<Nothing>parr(new Nothing[12]);会当掉程序
然后释放资源的时候不知道到底是利用delete pstr,还是 delete[] pstr;

然后收集了关于auto_ptr的几种注意事项:
1、auto_ptr不能共享所有权。
2、auto_ptr不能指向数组
3、auto_ptr不能作为容器的成员。
4、不能通过赋值操作来初始化auto_ptr
std::auto_ptr<int> p(new int(42));     //OK
std::auto_ptr<int> p = new int(42);    //ERROR
这是因为auto_ptr 的构造函数被定义为了explicit
5、不要把auto_ptr放入容器

1. 为什么需要智能指针?

简单的说,智能指针是为了实现类似于Java中的垃圾回收机制。Java的垃圾回收机制使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存区域之后,无需去关注应该何时何地释放内存,Java将会自动帮助回收。但是出于效率和其他原因(可能C++设计者不屑于这种傻瓜氏的编程方式),C++本身并没有这样的功能,其繁杂且易出错的内存管理也一直为广大程序员所诟病。

更进一步地说,智能指针的出现是为了满足管理类中指针成员的需要。包含指针成员的类需要特别注意复制控制和赋值操作,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。当类的实例在析构的时候,可能会导致垂悬指针问题。

管理类中指针成员的方法一般有两种方式:一种是采用值型类,这种类是给指针成员提供值语义(value semantics),当复制该值型对象时,会得到一个不同的新副本。这种方式典型的应用是string类。另外一种方式就是智能指针,实现这种方式的指针所指向的对象是共享的。

2. 智能指针的实现概述

智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

实现智能指针有两种经典策略:一是引入辅助类,二是使用句柄类。

3. 实现方式1:引入辅助类

这种方式定义一个单独的具体类(RefPtr)来封装指针和相应的引用计数。




#include<iostream>
using namespace std;

class Point                                       //基础对象类
{
public:
 Point(int xVal = 0, int yVal = 0):x(xVal),y(yVal) { cout<<"Point()"<<endl;}
 int getX() const { return x; }
 int getY() const { return y; }
 void setX(int xVal) { x = xVal; }
 void setY(int yVal) { y = yVal; }
 ~Point(){cout<<"~Point()"<<endl;}
private:
 int x,y;
};

class RefPtr                                  //辅助类
{//该类成员访问权限全部为private,因为不想让用户直接使用该类
 friend class SmartPtr;                                  //定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
 RefPtr(Point *ptr):p(ptr), count(1) {cout<<"RefPtr()"<<endl; }
 ~RefPtr() { delete p;cout<<"~RefPtr()"<<endl;  }

 int count;                                                     //引用计数
 Point *p;                                                      //基础对象指针
};

class SmartPtr                                             //智能指针类
{
public:
 SmartPtr(Point *ptr):rp(new RefPtr(ptr)) { cout<<" SmartPtr()"<<endl; }                                 //构造函数
 SmartPtr(const SmartPtr &sp):rp(sp.rp) { ++rp->count; }            //复制构造函数
 SmartPtr& operator=(const SmartPtr& rhs) {                              //重载赋值操作符
  ++rhs.rp->count;                                                                        //首先将右操作数引用计数加1,
  if(--rp->count == 0)                                                                     //然后将引用计数减1,可以应对自赋值
   delete rp;
  rp = rhs.rp;
  return *this;
 }
 ~SmartPtr() {                                            //析构函数
  if(--rp->count == 0)                                  //当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
   delete rp;
  cout<<" ~SmartPtr()"<<endl;
 }

private:
 RefPtr *rp;                                                //辅助类对象指针
};

int main()
{
 Point *p1 = new Point(10, 8);
 SmartPtr sp1(p1);
 //SmartPtr sp2(sp1);
 //Point *p2 = new Point(5, 5);
 //SmartPtr sp3(p2);
 //sp3 = sp1;

 return 0;
}



使用该方式的内存结构图如下:

智能指针 - 玉宝的爱 - 07118332 的博客

 

4. 实现方式2:使用句柄类

为了避免上面方案中每个使用指针的类自己去控制引用计数,可以用一个类把指针封装起来。封装好后,这个类对象可以出现在用户类使用指针的任何地方,表现为一个指针的行为。我们可以像指针一样使用它,而不用担心普通成员指针所带来的问题,我们把这样的类叫句柄类。在封装句柄类时,需要申请一个动态分配的引用计数空间,指针与引用计数分开存储。实现示例如下:




#include<iostream>
using namespace std;
class Point                                                  //基础对象类
{
public:
 Point(int xVal = 0, int yVal = 0):x(xVal),y(yVal) { cout<<"Point()"<<endl;}
 int getX() const { return x; }
 int getY() const { return y; }
 void setX(int xVal) { x = xVal; }
 void setY(int yVal) { y = yVal; }
 ~Point(){cout<<"~Point()"<<endl;}
public:
 virtual Point* clone() const {                    //虚函数,为了实现让句柄类在不知道对象的确切类型的情况下分配已知对象的新副本
  return new Point(*this);
 }
 
private:
 int x,y;
};

class D3Point : public Point                           //派生类
{
public:
 D3Point(int xVal, int yVal, int zVal):Point(xVal, yVal), z(zVal) { cout<<"D3Point()"<<endl;}
 int getZ() const { return z; }
 void setZ(int zVal) { z = zVal; }
 ~D3Point(){ cout<<"~D3Point()"<<endl;}
public:
 D3Point* clone() const {                             //虚函数,为了实现让句柄类在不知道对象的确切类型的情况下分配已知对象的新副本
  return new D3Point(*this);
 }

private:
 int z;
};

class SmartPtr
{
public:
 SmartPtr(Point *ptr = 0):p(ptr), count(new int(1)) { cout<<"SmartPtr()"<<endl; }                                         //构造函数
 SmartPtr(Point &point):p(point.clone()), count(new int(1)) { }                          //构造函数
 SmartPtr(const SmartPtr &sp):p(sp.p), count(sp.count) { ++*count; }             //复制构造函数
 SmartPtr& operator=(const SmartPtr &sp) {                                                   //重载赋值操作符
  ++*sp.count;                                           //首先将右操作数引用计数加1,
  decr_use();                                             //然后将引用计数减1,可以应对自赋值
  p = sp.p;
  count = sp.count;
  return *this;
 }
 ~SmartPtr() {    cout<<"~SmartPtr()"<<endl;                                    //析构函数
  decr_use();
 }

public:                                   //一般情况下不会实现这两个操作符,因为我们不希望用户直接操纵基础对象指针
 const Point* operator->() const {
  if(p) return p;
  else throw logic_error("Unbound Point");
 }
 const Point& operator*() const {
  if(p) return *p;
  else throw logic_error("Unbound Point");
 }

private:
 void decr_use() {
  if(--*count == 0)
  {
   delete p;
   delete count;
  }
 }

private:
 Point *p;                                      //基础对象指针
 int *count;                                   //指向引用计数的指针
};

int main()
{
 //Point *p1 = new Point(10, 8);
 //SmartPtr sp1(p1);
 //SmartPtr sp2(sp1);
 D3Point *p2 = new D3Point(5, 5, 0);
SmartPtr sp3(p2);

 return 0;
}

使用该方式的内存结构图如下:

智能指针 - 玉宝的爱 - 07118332 的博客




Boost中的智能指针

  智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。

  事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP (Extremely Smart Pointers)创建一般智能指针的方法,但是并没有涵盖进来。

  智能指针的大部分使用是用于生存期控制,阶段控制。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。

  这样的一个类来自标准库:std::auto_ptr。它是为解决资源所有权问题设计的,但是缺少对引用数和数组的支持。并且,std::auto_ptr在被复制的时候会传输所有权。在大多数情况下,你需要更多的和/或者是不同的功能。这时就需要加入smart_ptr类。


从而推荐的是boost的shared_ptr,然后看完shared_ptr关于智能指针的介绍与例子。
5种针对auto_ptr不足的指针如下:需要详细了解可以去查看相当文档,与测试新代码。

scoped_ptr<boost/scoped_ptr.hpp>简单的单一对象的唯一所有权。不可拷贝。scoped_array<boost/scoped_array.hpp>简单的数组的唯一所有权。不可拷贝。shared_ptr<boost/shared_ptr.hpp>在多个指针间共享的对象所有权。shared_array<boost/shared_array.hpp>在多个指针间共享的数组所有权。weak_ptr<boost/weak_ptr.hpp>一个属于 shared_ptr 的对象的无所有权的观察者。intrusive_ptr<boost/intrusive_ptr.hpp>带有一个侵入式引用计数的对象的共享所有权。
1. shared_ptr是Boost库所提供的一个智能指针的实现,shared_ptr就是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针.
2. shared_ptr比auto_ptr更安全
3. shared_ptr是可以拷贝和赋值的,拷贝行为也是等价的,并且可以被比较,这意味这它可被放入标准库的一般容器(vector,list)和关联容器中(map)。

关于shared_ptr的使用其实和auto_ptr差不多,只是实现上有差别,关于shared_ptr的定义就不贴代码了,以为内开源,可以网上找
1、shared_ptr<T> p(new Y);


要了解更多关于auto_ptr的信息,可以查看more effective c++ 的p158页条款28
要了解shared_ptr 类模板信息,可以查看boost 1.37.0中文文档,而且支持数组的shared_array 类模板



  smart_ptr 类

  在Boost中的智能指针有:

  。scoped_ptr,用于处理单个对象的唯一所有权;与std::auto_ptr不同的是,scoped_ptr可以被复制。

  。scoped_array,与scoped_ptr类似,但是用来处理数组的

  。shared_ptr,允许共享对象所有权

  。shared_array,允许共享数组所有权

  scoped_ptr

  scoped_ptr智能指针与std::auto_ptr不同,因为它是不传递所有权的。事实上它明确禁止任何想要这样做的企图!这在你需要确保指针任何时候只有一个拥有者时的任何一种情境下都是非常重要的。如果不去使用scoped_ptr,你可能倾向于使用std::auto_ptr,让我们先看看下面的代码:

  auto_ptr MyOwnString?

  (new string("This is mine to keep!"));

  auto_ptr NoItsMine?(MyOwnString?);

  cout << *MyOwnString << endl; // Boom

  这段代码显然将不能编译通过,因为字符串的所有权被传给了NoItsMine。这不是std::auto_ptr的设计缺陷—而是一个特性。尽管如此,当你需要MyOwnString达到上面的代码预期的工作效果的话,你可以使用scoped_ptr:

  scoped_ptr MyOwnString?

  (new string("This is mine to keep for real!"));

  // Compiler error - there is no copy constructor.

  scoped_ptr TryingToTakeItAnyway?

  (MyOwnString?);

  scoped_ptr通过从boost::noncopyable继承来完成这个行为(可以查看Boost.utility库)。不可复制类声明复制构造函数并将赋值操作符声明为private类型。

  scoped_array

  scoped_array与scoped_ptr显然是意义等价的,但是是用来处理数组的。在这一点标准库并没有考虑—除非你当然可以使用std::vector,在大多数情况下这样做是可以的。

  用法和scoped_ptr类似:

  typedef tuples::tupleint> ArrayTuple?;

  scoped_array MyArray?(new ArrayTuple?[10]);

  tuples::get<0>(MyArray?[5]) ="The library Tuples is also part of Boost";

  tuple是元素的集合—例如两倍,三倍,和四倍。Tuple的典型用法是从函数返回多个值。Boost Tuple库可以被认为是标准库两倍的扩展,目前它与近10个tuple元素一起工作。支持tuple流,比较,赋值,卸包等等。

  当scoped_array越界的时候,delete[]将被正确的调用。这就避免了一个常见错误,即是调用错误的操作符delete。

  shared_ptr

  这里有一个你在标准库中找不到的—引用数智能指针。大部分人都应当有过使用智能指针的经历,并且已经有很多关于引用数的文章。最重要的一个细节是引用数是如何被执行的—插入,意思是说你将引用计数的功能添加给类,或者是非插入,意思是说你不这样做。Boost shared_ptr是非插入类型的,这个实现使用一个从堆中分配来的引用计数器。关于提供参数化策略使得对任何情况都极为适合的讨论很多了,但是最终讨论的结果是决定反对聚焦于可用性。可是不要指望讨论的结果能够结束。

  shared_ptr完成了你所希望的工作:他负责在不使用实例时删除由它指向的对象(pointee),并且它可以自由的共享它指向的对象(pointee)。

  void PrintIfString?(const any& Any) {

  if (const shared_ptr* s =

  any_cast >(&Any)) {

  cout << **s << endl;

  }

  }

  int main(int argc, char* argv[])

  {

  std::vector Stuff;

  shared_ptr SharedString1?

  (new string("Share me. By the way,

  Boost.any is another useful Boost

  library"));

  shared_ptr SharedString2?

  (SharedString1?);

  shared_ptr SharedInt1?

  (new int(42));

  shared_ptr SharedInt2?

  (SharedInt1?);

  Stuff.push_back(SharedString1?);

  Stuff.push_back(SharedString2?);

  Stuff.push_back(SharedInt1?);

  Stuff.push_back(SharedInt2?);

  // Print the strings

  for_each(Stuff.begin(), Stuff.end(),

  PrintIfString?);

  Stuff.clear();

  // The pointees of the shared_ptr's

  // will be released on leaving scope

  // shared_ptr的pointee离开这个范围后将被释放

  return 0;

  }

  any库提供了存储所有东西的方法[2]HYPERLINK "file:///C:Documents%20and%20SettingsAdministrator桌面My%20Documents新建 CUJhtml20.04karlsson%22%20l"[4]。在包含类型中需要的是它们是可拷贝构造的(CopyConstructible),析构函数这里绝对不能引发,他们应当是可赋值的。我们如何存储和传递“所有事物”?无区别类型(读作void*)可以涉及到所有的事物,但这将意味着将类型安全(与知识)抛之脑后。any库提供类型安全。所有满足any需求的类型都能够被赋值,但是解开的时候需要知道解开类型。any_cast是解开由any保存着的值的钥匙,any_cast与dynamic_cast的工作机制是类似的—指针类型的类型转换通过返回一个空指针成功或者失败,因此赋值类型的类型转换抛出一个异常(bad_any_cast)而失败。

  shared_array

  shared_array与shared_ptr作用是相同的,只是它是用于处理数组的。

  shared_array MyStrings?( new Base[20] );

  深入shared_ptr实现

  创建一个简单的智能指针是非常容易的。但是创建一个能够在大多数编译器下通过的智能指针就有些难度了。而创建同时又考虑异常安全就更为困难了。Boost::shared_ptr这些全都做到了,下面便是它如何做到这一切的。(请注意:所有的include,断开编译器处理,以及这个实现的部分内容被省略掉了,但你可以在Boost.smart_ptr当中找到它们)。

  首先,类的定义:很显然,智能指针是(几乎总是)模板。

  template class shared_ptr {

  公共接口是:

  explicit shared_ptr(T* p =0) : px(p) {

  // fix: prevent leak if new throws

  try { pn = new long(1); }

  catch (...) { checked_delete(p); throw; }

  }

  现在看来,在构造函数当中两件事情是容易被忽略的。构造函数是explicit的,就像大多数的构造函数一样可以带有一个参数。另外一个值得注意的是引用数的堆分配是由一个try-catch块保护的。如果没有这个,你得到的将是一个有缺陷的智能指针,如果引用数没有能够成功分配,它将不能正常完成它自己的工作。

  ~shared_ptr() { dispose(); }

  析构函数执行另外一个重要任务:如果引用数下降到零,它应当能够安全的删除指向的对象(pointee)。析构函数将这个重要任务委托给了另外一个方法:dispose。

  void dispose() { if (—*pn == 0)

  { checked_delete(px); delete pn; } }

  正如你所看到的,引用数(pn)在减少。如果它减少到零,checked_delete在所指对象 (px)上被调用,而后引用数(pn)也被删除了。

  那么,checked_delete执行什么功能呢?这个便捷的函数(你可以在Boost.utility中找到)确保指针代表的是一个完整的类型。在你的智能指针类当中有这个么?

  这是第一个赋值运算符:

  template shared_ptr& operator=

  (const shared_ptr& r) {

  share(r.px,r.pn);

  return *this;

  }

  这是成员模版,如果不是这样,有两种情况:

  1. 如果没有参数化复制构造函数,类型赋值Base = Derived无效。

  2. 如果有参数化复制构造函数,类型赋值将生效,但同时创建了一个不必要的临时smart_ptr。

  这再一次的展示给你为什么不应当加入你自己的智能指针的一个非常好的原因—这些都不是很明显的问题。

  赋值运算符的实际工作是由share函数完成的:

  void share(T* rpx, long* rpn) {

  if (pn = rpn) { // Q: why not px = rpx?

  // A: fails when both == 0

  ++*rpn; // done before dispose() in case

  // rpn transitively dependent on

  // *this (bug reported by Ken Johnson)

  dispose();

  px = rpx;

  pn = rpn;

  }

  }

  需要注意的是自我赋值(更准确地说是自我共享)是通过比较引用数完成的,而不是通过指针。为什么这样呢?因为它们两者都可以是零,但不一定是一样的。

  template shared_ptr

  (const shared_ptr& r) : px(r.px) { // never throws

  ++*(pn = r.pn);

  }

  这个版本是一个模版化的拷贝构造和函数。可以看看上面的讨论来了解为什么要这样做。

  赋值运算符以及赋值构造函数在这里同样也有一个非模版化的版本:

  shared_ptr(const shared_ptr& r) :

  // never throws

  px(r.px) { ++*(pn = r.pn); }

  shared_ptr& operator=

  (const shared_ptr& r) {

  share(r.px,r.pn);

  return *this;

  }

  reset函数就像他的名字那样,重新设置所指对象(pointee)。在将要离开作用域的时候,如果你需要销毁所指对象(pointee)它将非常方便的帮你完成,或者简单的使缓存中的值失效。

  void reset(T* p=0) {

  // fix: self-assignment safe

  if ( px == p ) return;

  if (—*pn == 0)

  { checked_delete(px); }

  else { // allocate new reference

  // counter

  // fix: prevent leak if new throws

  try { pn = new long; }

  catch (...) {

  // undo effect of —*pn above to

  // meet effects guarantee

  ++*pn;

  checked_delete(p);

  throw;

  } // catch

  } // allocate new reference counter

  *pn = 1;

  px = p;

  } // reset

  这里仍然请注意避免潜在的内存泄漏问题和保持异常安全的处理手段。

  这样你就有了使得智能指针发挥其“智能”的运算符:

  // never throws

  T& operator*() const { return *px; }

  // never throws

  T* operator->() const { return px; }

  // never throws

  T* get() const { return px; }

  这仅仅是一个注释:有的智能指针实现从类型转换运算符到T*的转换。这不是一个好主意,这样做常会使你因此受到伤害。虽然get在这里看上去很不舒服,但它阻止了编译器同你玩游戏。

  我记得是Andrei Alexandrescu说的:“如果你的智能指针工作起来和哑指针没什么两样,那它就是哑指针。”简直是太对了。

  这里有一些非常好的函数,我们就拿它们来作为本文的结束吧。

  long use_count() const

  { return *pn; } // never throws

  bool unique() const

  { return *pn == 1; } // never throws

  函数的名字已经说明了它的功能了,对么?

  关于Boost.smart_ptr还有很多应当说明的(比如std::swap和std::less的特化,与std::auto_ptr榜定在一起确保兼容性以及便捷性的成员,等等),由于篇幅限制不能再继续介绍了。详细内容请参考Boost distribution ()的smart_ptr.hpp。即使没有那些其它的内容,你不认为他的确是一个非常智能的指针么?

 

野指针:

定义:

“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。有个良好的编程习惯是避免“野指针”的唯一方法。

 

野指针的成因主要有三种:

  一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

  二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。例:

  #include <stdio.h>

  #include <string.h>

  #include <malloc.h>

  int main(void)

  {

  char *p = (char *) malloc(100);

  strcpy(p, "hello");

  free(p); // p 所指的内存被释放,但是p所指的地址仍然不变,原来的内存变为“垃圾”内存(不可用内存

  if(p != NULL) // 没有起到防错作用

  strcpy(p, "world");

  for(i=0;i<5;i++) //i=5后为乱码

  printf("%c",*(p+i));

  printf("\n");

  }

  另外一个要注意的问题:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

  三、指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

  class A

  {

  public:

  void Func(void){ cout << “Func of class A” << endl; }

  };

  class B

  {

  public:

  A *p;

  void Test(void)

  {

  A a;

  p = &a; // 注意 a 的生命期 ,只在这个函数Test中,而不是整个class B

  }

  void Test1()

  {

  p->Func(); // p 是“野指针”

  }

  }

  函数 Test1 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,所以 p 就成了“野指针” 。

原创粉丝点击