1. 程式人生 > >C++之智能指針20170920

C++之智能指針20170920

智能指針類 指針 n) emp argv before == 表示 解決

/******************************************************************************************************************/

一、C++智能指針_自己實現智能指針

1.使用局部變量結合new的方式,防止new導致的內存泄漏

class sp

{

private:

Person *p;

public:

sp() : p(0) {}//表明sp的構造函數 繼承person的無參構造函數

sp(Person *other)

   {

cout<<"sp(const Person *other)"<<endl;

p = other;

}

~sp()

{

cout<<"~sp()"<<endl;

if (p)

delete p;

}

  Person *operator->()//重載->

  {

return p;

}

};

void test_func(void)

{

sp s = new Person();//分配的堆空間只是當作參數傳遞進去(傳遞給構造函數)了

   s->printInfo();

}

分配的堆空間只是當作參數傳遞進去(傳遞給構造函數)了,s執行完後調用s的析構函數(局部變量,棧裏),析構函數中又釋放了內存就沒有泄漏了

以後代碼中需要person指針就可以使用sp代替,不用擔心內存泄漏

/* 相當於:

* 1. Person *p = new Person();

* 2. sp tmp(p); ==> sp(Person *other)

* 3.

* 3.1 sp other(tmp); ==> sp(sp &other2)

問題在於: sp &other2 = tmp; // 錯誤語法,因為引用無法等於臨時變量

const sp& other2 = tmp; //ok

* 或

* 3.2 sp other(tmp ==> Person*); ==>sp(Person *other)

*/

sp other = new Person();//編譯器優化,直接把2,3部合並為一步

for (i = 0; i < 2; i++)

test_func(other);//這裏執行兩次會崩潰,原因在於第一次執行完後,把p指向的對象釋放了,第二次再使用該指針自然會奔潰。

2.解決辦法,引入引用計數,也就是再增加一個整數成員,當發現有人引用對象時 該值加1,如果使用完 減一,如果等於0再釋放(解決過早釋放問題)

class Person {

private:

int count;

public:

void incStrong(){ count++; }

void decStrong(){ count--; }

int getStrongCount(){ return count;}

  Person() : count(0)//表示構造時count值默認值是0

  {

cout <<"Pserson()"<<endl;

}

~Person()

{

cout << "~Person()"<<endl;

}

void printInfo(void)

{

cout<<"just a test function"<<endl;

}

};

class sp

{

private:

Person *p;

public:

sp() : p(0) {}

sp(Person *other)

{

cout<<"sp(Person *other)"<<endl;

p = other;

p->incStrong();

}

sp(const sp &other)

{

cout<<"sp(const sp &other)"<<endl;

p = other.p;

p->incStrong();

}

~sp()

{

cout<<"~sp()"<<endl;

if (p)

{

p->decStrong();

if (p->getStrongCount() == 0)

{

delete p;

p = NULL;

}

}

}

Person *operator->()

{

return p;

}

};

void test_func(sp &other)

{

sp s = other;

cout<<"In test_func: "<<s->getStrongCount()<<endl;

s->printInfo();

//Person *p = new Person();

//p->printInfo();

//delete p;

}

int main(int argc, char **argv)

{

int i;

sp other = new Person();

cout<<"Before call test_func: "<<other->getStrongCount()<<endl;

for (i = 0; i < 2; i++)

{

test_func(other);

cout<<"After call test_func: "<<other->getStrongCount()<<endl;

}

return 0;//main函數執行完後,再次減一,這時就把person對象銷毀了

}

3. 重載*,供下面使用

Person& operator*()//重載*,供下面使用

{//不使用引用,返回的是值,就又會去構造對象(臨時對象,見上述引用章節)

return *p;

}

/* 少用"Person *"; 用"sp"來代替"Person *"

* Person *per;

* 有2種操作: per->XXXx, (*per).XXX

* sp也應該有這2中操作:

* sp->XXX, (*sp).XXX*

*/

sp other = new Person();

(*other).printInfo();

4.優化:將count的操作獨立出去,單獨為一個類,其他類如person直接繼承,就可以操作count的類。

5.優化:為了讓智能指針類更通用,應當把類修改為類模版,這樣就不局限於內部的指針僅指向person。

template<typename T>

class sp

{

private:

T *p;

}

最終就可以實現, 用"sp<Person>"來代替"Person *

而且不需要delete,由系統釋放

/******************************************************************************************************************/

二、C++智能指針_輕量級指針

count++不是原子操作,所以在多線程的情況下是不安全的

所以引入輕量級指針(保證原子操作,保證操作count值線程安全):

1.使用安卓提供的輕量級智能指針的代碼:

using namespace android::RSC;//使用安卓命名空間下的RSC命名空間

class Person : public LightRefBase<Person>

{//指定模版參數為person

public:

Person() {

cout <<"Pserson()"<<endl;

}

~Person()

{

cout << "~Person()"<<endl;

}

void printInfo(void)

{

cout<<"just a test function"<<endl;

}

};

2.被調用的安卓RefBase.h中的部分代碼:

template <class T>

class LightRefBase

{

public:

inline LightRefBase() : mCount(0) { }

inline void incStrong(__attribute__((unused)) const void* id) const {

__sync_fetch_and_add(&mCount, 1);//__sync_fetch_and_add是原子操作

}

inline void decStrong(__attribute__((unused)) const void* id) const {

if (__sync_fetch_and_sub(&mCount, 1) == 1)

  {//__sync_fetch_and_sub是原子操作

delete static_cast<const T*>(this);

}

}

3.註意輕量級指針只是對count的操作是線程安全的,但是對於操作的對象即智能指針還是不安全的,多線程情況下只能自己來保證智能指針的使用是線程安全的,即智能指針指向的對象的操作是線程安全的。

為啥叫輕量指針,是因為代碼簡單,使用方便。

/******************************************************************************************************************/

三、C++智能指針_弱指針的引入

輕量指針,代碼簡單,使用方便,但是

當有兩個智能指針互相引用的時候,由於執行完引用計數不為零,就沒辦法釋放,會造成內存泄漏:

/* 如果對象裏含有其他對象成員:

* 構造時: 先構造其他對象成員, 再構造對象本身

* 析構時: 順序剛好相反

*/

void test_func()

{

/* 1. 對於 new Person()

* 1.1 Person對象裏的father先被構造

* 1.2 Person對象裏的son被構造

* 1.3 Person對象本身

* 2. Person對象的指針傳給"sp<Person> father"

* 導致: sp(T* other) 被調用

* 它增加了這個Person對象的引用計數(現在此值等於1)

*/

sp<Person> father = new Person();

/* 1. 對於 new Person()

* 1.1 Person對象裏的father先被構造

* 1.2 Person對象裏的son被構造

* 1.3 Person對象本身

* 2. Person對象的指針傳給"sp<Person> son"

* 導致: sp(T* other) 被調用

* 它增加了這個Person對象的引用計數(現在此值等於1)

*/

sp<Person> son = new Person();

/* 它是一個"=" : this->son = son

* "="被重載, 它會再次增加該Person對象的引用計數

* 所以son對應的Person對象的引用計數增加為2

*/

father->setSon(son);

/* 它是一個"=" : this->father = father

* "="被重載, 它會再次增加該Person對象的引用計數

* 所以father對應的Person對象的引用計數增加為2

*/

son->setFather(father);

/* 當test_func執行完時, father和son被析構

* 1. 先看father:

* ~sp(): decStrong, 裏面會將計數值減1 , father對應的Person的計數值等於1, 還沒等於0, 所以沒有delete

* 2. 對於son:

* ~sp(): decStrong, 裏面會將計數值減1 , son對應的Person的計數值等於1, 還沒等於0, 所以沒有delete

*/

}

對於交叉引用導致內存泄漏的問題,如果兩個指針是弱引用,也就是不增加對方的引用計數值,那麽就不會造成內存泄漏,因此只能引用弱指針(弱引用)才能解決這個問題,

強指針(強引用):A指向B,A決定B的生死

弱指針(弱引用):A指向B,A不能決定B的生死

/******************************************************************************************************************/

四、C++智能指針_強弱指針的實現與使用

強弱指針的實現:

加入引用計數mWeak成員並提供加減操作,同時加入flag成員表示當前是強還是弱,具體實現見安卓源碼.

1.源碼說明:

I、在RefBase.h中的class RefBase中成員指針指向weakref_impl,也就是強弱指針的實現,weakref_impl繼承於weakref_type(操作成員的抽象類),也就是說weakref_impl就是具體的實現類。

具體也可以說,

強弱指針的實現主要是加入引用計數mWeak成員並提供加減操作,同時加入flag成員表示當前是強還是弱

再將強弱指針的實現抽象出抽象類,具體實現類weakref_impl繼承weakref_type抽象類,並在RefBase.h中的class RefBase中定義指向weakref_impl的指針。

II、這樣實現的原因:

a.頭文件中不想看到私有成員,只想看到接口,則把接口抽象出來為抽象類(放在頭文件中),然後剩下的獨立為實現類(放在cpp文件中),實現類繼承抽象類,抽象類對外。

b.依賴對象的時候,最好依賴指針,因為直接依賴對象,當對象發生變化就會跟著發生變化,指針只占四個字節就不會跟著變化,對空間布局沒有影響

2.強弱指針的使用:

I、使用安卓裏現成的,

頭文件:

system/core/include/utils

system/core/include/cutils

cpp:

system/core/libutils/RefBase.cpp

makefile:

%.o : %.cpp

g++ -c -o [email protected] $< -I include //-I include表示包含當前目錄下的include目錄

//查找頭文件不會遞歸的往子目錄查找,所以要麽這裏加要麽代碼中加上 目錄名/XXX.h

例:

int main(int argc, char **argv)

{

wp<Person> s = new Person();//弱指針

//s->printInfo(); /* 出錯, wp沒有重載"->", "*" */

//(*s).printInfo(); /* 出錯, wp沒有重載"->", "*" */

sp<Person> s2 = s.promote();//提升為強指針,這樣就可以調用了

   if (s2 != 0) {

s2->printInfo();

}

return 0;

}

II、使用弱指針可以解決交叉引用導致內存泄漏的問題,但是想使用引用的對象裏的函數,就必須先升級為強指針才可以調用

#include <iostream>

#include <string.h>

#include <unistd.h>

#include <utils/RefBase.h>

using namespace std;

using namespace android;

class Person : public RefBase

{//繼承RefBase

private:

char *name;

wp<Person> father;

wp<Person> son;

public:

Person() {

cout <<"Pserson()"<<endl;

}

Person(char *name) {

cout <<"Pserson(char *name)"<<endl;

this->name = name;

}

~Person()

{

cout << "~Person()"<<endl;

}

void setFather(sp<Person> &father)

{

this->father = father;

}

void setSon(sp<Person> &son)

{

this->son = son;

}

char *getName(void)

{

return name;

}

void printInfo(void)

{

sp<Person> f = father.promote();

sp<Person> s = son.promote();

//cout<<"just a test function"<<endl;

cout<<"I am "<<name<<endl;

if (f != 0)

cout<<"My Father is "<<f->getName()<<endl;

if (s != 0)

cout<<"My Son is "<<s->getName()<<endl;

}

};

/* 如果對象裏含有其他對象成員:

* 構造時: 先構造其他對象成員, 再構造對象本身

* 析構時: 順序剛好相反

*/

void test_func()

{

/* 1. 對於 new Person()

* 1.1 Person對象裏的father先被構造

* 1.2 Person對象裏的son被構造

* 1.3 Person對象本身

* 2. Person對象的指針傳給"sp<Person> father"

* 導致: sp(T* other) 被調用

* 它增加了這個Person對象的引用計數(現在此值等於1)

*/

sp<Person> father = new Person("LiYiShi");

/* 1. 對於 new Person()

* 1.1 Person對象裏的father先被構造

* 1.2 Person對象裏的son被構造

* 1.3 Person對象本身

* 2. Person對象的指針傳給"sp<Person> son"

* 導致: sp(T* other) 被調用

* 它增加了這個Person對象的引用計數(現在此值等於1)

*/

sp<Person> son = new Person("LiErShi");

father->setSon(son);

son->setFather(father);

father->printInfo();

son->printInfo();

}

int main(int argc, char **argv)

{

test_func();

return 0;

}

C++之智能指針20170920