使用智能指針shared_ptr注意事項

發現一個總結的非常不錯的文章,轉一下。先補一條自己的補充:

一、子類轉父類問題

前提是基於錯誤的正常,父類未寫virtual析構,子類有虛函數。手動管理時類型明確遮掩了問題,智能指針管理時,是根據傳入類型來管理,如果先行轉換爲了父類,暴露問題。

class Base
{
public:
        int a = 1;
};
class B : public Base
{
public:
        int b = 2;
        virtual ~B(){}
};
int main()
{
        B* pb = new B;
        //傳到底層,統一爲A
        Base* pa = pb;
        //
        shared_ptr<Base> p1(pa);            //交由智能指針釋放,轉換爲了父類,析構異常
        shared_ptr<Base> p1(pb);            //傳原類型沒有問題
        //delete pb;                        //主動釋放正常
        return 0;
}


其他情況見他人總結

原文:http://www.cnblogs.com/sunshinewave/p/5296666.html

shared_ptr 的使用及注意事項

Posted on 2016-03-19 22:56 sunshinewave 閱讀(4317) 評論(0編輯 收藏

1. 聲明
#include <boost/shared_ptr.hpp>

class UsersBitmap {

...

}

typedef boost::shared_ptr<UsersBitmap> UsersBitmapPtr;

2. 使用

UsersBitmapPtr login_users_;
UsersBitmapPtr temp_login_users(new UsersBitmap());    //指向對象

login_users_.reset(new UsersBitmap());     //指針指向新的地方

login_users_.reset();  //指針置空

 

///////////////////////////////////////////////////////////////////////////

////////////////////////////////////

雖然boost.shared_ptr是個非常好的東西,使用它可以使得c++程序不需要考慮內存釋放的問題,但是還是有很多必須注意的地方。下面羅列了一些本人在實際工作中經常碰到的使用shared_ptr出問題的幾種情況。 

1. shared_ptr多次引用同一數據,如下:
{
int* pInt = new int[100];
boost::shared_ptr<int> sp1(pInt);
// 一些其它代碼之後…
boost::shared_ptr<int> sp2(pInt);
}

這種情況在實際中是很容易發生的,結果也是非常致命的,它會導致兩次釋放同一塊內存,而破壞堆。 

2.使用shared_ptr包裝this指針帶來的問題,如下:

class tester 
{
public:
  tester()
  ~tester()
  {
    std::cout << "析構函數被調用!\n"; 
  }
public:
  boost::shared_ptr<tester> sget()
  {
    return boost::shared_ptr<tester>(this);
  }
};

int main()
{
  tester t;
  boost::shared_ptr<tester> sp =  t.sget(); // …
  return 0;
}

也將導致兩次釋放t對象破壞堆棧,一次是出棧時析構,一次就是shared_ptr析構。若有這種需要,可以使用下面代碼。

class tester : public boost::enable_shared_from_this<tester>
{
public:
  tester()
  ~tester()
  {
  std::cout << "析構函數被調用!\n"; 
  }
public:
  boost::shared_ptr<tester> sget()
  {
  return shared_from_this();
  }
};

int main()
{
  boost::shared_ptr<tester> sp(new tester);
  // 正確使用sp 指針。
  sp->sget();
  return 0;
}

3. shared_ptr循環引用導致內存泄露,代碼如下:

class parent;
class child; 

typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<child> child_ptr; 

class parent
{
public:
       ~parent() { 
              std::cout <<"父類析構函數被調用.\n"; 
       }
public:
       child_ptr children;
};

class child
{
public:
       ~child() { 
              std::cout <<"子類析構函數被調用.\n"; 
       }
public:
       parent_ptr parent;
};

int main()
{
  parent_ptr father(new parent());
  child_ptr son(new child);
  // 父子互相引用。
  father->children = son;
  son->parent = father;
  return 0;
}

如上代碼,將在程序退出前,father的引用計數爲2,son的計數也爲2,退出時,shared_ptr所作操作就是簡單的將計數減1,如果爲0則釋放,顯然,這個情況下,引用計數不爲0,於是造成father和son所指向的內存得不到釋放,導致內存泄露。 

4. 在多線程程序中使用shared_ptr應注意的問題。代碼如下:

class tester 
{
public:
  tester() {}
  ~tester() {}
  // 更多的函數定義…
};

void fun(boost::shared_ptr<tester> sp)
{
  // !!!在這大量使用sp指針.
  boost::shared_ptr<tester> tmp = sp;
}

int main()
{
  boost::shared_ptr<tester> sp1(new tester);
  // 開啓兩個線程,並將智能指針傳入使用。
  boost::thread t1(boost::bind(&fun, sp1));
  boost::thread t2(boost::bind(&fun, sp1));
  t1.join();
  t2.join();
  return 0;
}

這個代碼帶來的問題很顯然,由於多線程同是訪問智能指針,並將其賦值到其它同類智能指針時,很可能發生兩個線程同時在操作引用計數(但並不一定絕對發生),而導致計數失敗或無效等情況,從而導致程序崩潰,如若不知根源,就無法查找這個bug,那就只能向上帝祈禱程序能正常運行。

可能一般情況下並不會寫出上面這樣的代碼,但是下面這種代碼與上面的代碼同樣,如下:

class tester 
{
public:
  tester() {}
  ~tester() {}
public:
  boost::shared_ptr<int> m_spData; // 可能其它類型。
};

tester gObject;

void fun(void)
{
  // !!!在這大量使用sp指針.
  boost::shared_ptr<int> tmp = gObject.m_spData;
}

int main()
{
  // 多線程。
  boost::thread t1(&fun);
  boost::thread t2(&fun);
  t1.join();
  t2.join();
  return 0;
}

情況是一樣的。要解決這類問題的辦法也很簡單,使用boost.weak_ptr就可以很方便解決這個問題。第一種情況修改代碼如下:

class tester 
{
public:
  tester() {}
  ~tester() {}
  // 更多的函數定義…
};

void fun(boost::weak_ptr<tester> wp)
{
  boost::shared_ptr<tester> sp = wp.lock;
  if (sp)
  {
    // 在這裏可以安全的使用sp指針.
  }
  else
  {
    std::cout << “指針已被釋放!” << std::endl;
  }


int main()
{
  boost::shared_ptr<tester> sp1(new tester);
  boost.weak_ptr<tester> wp(sp1);
  // 開啓兩個線程,並將智能指針傳入使用。
  boost::thread t1(boost::bind(&fun, wp));
  boost::thread t2(boost::bind(&fun, wp));
  t1.join();
  t2.join();
  return 0;
}

boost.weak_ptr指針功能一點都不weak,weak_ptr是一種可構造、可賦值以不增加引用計數來管理shared_ptr的指針,它可以方便的轉回到shared_ptr指針,使用weak_ptr.lock函數就可以得到一個shared_ptr的指針,如果該指針已經被其它地方釋放,它則返回一個空的shared_ptr,也可以使用weak_ptr.expired()來判斷一個指針是否被釋放。

boost.weak_ptr不僅可以解決多線程訪問帶來的安全問題,而且還可以解決上面第三個問題循環引用。Children類代碼修改如下,即可打破循環引用:

class child
{
public:
  ~child() { 
   std::cout <<"子類析構函數被調用.\n"; 
  }
public:
  boost::weak_ptr<parent> parent;
};

因爲boost::weak_ptr不增加引用計數,所以可以在退出函數域時,正確的析構。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章