程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 關於C++為什麼不參加渣滓回收機制解析

關於C++為什麼不參加渣滓回收機制解析

編輯:關於C++

關於C++為什麼不參加渣滓回收機制解析。本站提示廣大學習愛好者:(關於C++為什麼不參加渣滓回收機制解析)文章只能為提供參考,不一定能成為您想要的結果。以下是關於C++為什麼不參加渣滓回收機制解析正文


Java的喜好者們常常批判C++中沒有提供與Java相似的渣滓回收(Gabage Collector)機制(這很正常,正如C++的喜好者有時也攻擊Java沒有這個沒有那個,或許這個不行那個不夠好),招致C++中對靜態存儲的官吏稱為順序員的噩夢,不是嗎?你常常聽到的是內存遺失(memory leak)和合法指針存取,這一定令你很頭疼,而且你又不能丟棄指針帶來的靈敏性。

在本文中,我並不想揭露Java提供的渣滓回收機制的天生缺陷,而是指出了C++中引入渣滓回收的可行性。請讀者留意,這裡引見的辦法更多的是基於以後規范和庫設計的角度,而不是要求修正言語定義或許擴展編譯器。

什麼是渣滓回收?

作為支持指針的編程言語,C++將靜態管理存儲器資源的便當性交給了順序員。在運用指針方式的對象時(請留意,由於援用在初始化後不能更改援用目的的言語機制的限制,多態性使用大少數狀況下依賴於指針停止),順序員必需自己完成存儲器的分配、運用和釋放,言語自身在此進程中不能提供任何協助,也許除了依照你的要求正確的和操作零碎密切協作,完成實踐的存儲器管理。規范文本中,屢次提到了“未定義(undefined)”,而這大少數狀況下和指針相關。

某些言語提供了渣滓回收機制,也就是說順序員僅擔任分配存儲器和運用,而由言語自身擔任釋放不再運用的存儲器,這樣順序員就從厭惡的存儲器管理的任務中脫身了。但是C++並沒有提供相似的機制,C++的設計者Bjarne Stroustrup在我所知的獨一一本引見言語設計的思想和哲學的著作《The Design and Evolution of C++》(中譯本:C++言語的設計和演化)中花了一個大節討論這個特性。簡而言之,Bjarne自己以為,

“我有意這樣設計C++,使它不依賴於自動渣滓回收(通常就直接說渣滓回收)。這是基於自己對渣滓回收零碎的經歷,我很懼怕那種嚴重的空間和時間開支,也懼怕由於完成和移植渣滓回收零碎而帶來的復雜性。還有,渣滓回收將使C++不合適做許多底層的任務,而這卻正是它的一個設計目的。但我喜歡渣滓回收的思想,它是一種機制,可以簡化設計、掃除掉許多發生錯誤的本源。

需求渣滓回收的根本理由是很容易了解的:用戶的運用方便以及比用戶提供的存儲管理形式更牢靠。而支持渣滓回收的理由也有很多,但都不是最基本的,而是關於完成和效率方面的。

曾經有充沛多的論據可以反駁:每個使用在有了渣滓回收之後會做的更好些。相似的,也有充沛的論據可以支持:沒有使用能夠由於有了渣滓回收而做得更好。

並不是每個順序都需求永遠無休止的運轉下去;並不是一切的代碼都是根底性的庫代碼;關於許多使用而言,呈現一點存儲流失是可以承受的;許多使用可以管理自己的存儲,而不需求渣滓回收或許其他與之相關的技術,如援用計數等。

我的結論是,從准繩上和可行性上說,渣滓回收都是需求的。但是對明天的用戶以及普遍的運用和硬件而言,我們還無法接受將C++的語義和它的根本庫定義在渣滓回收零碎之上的擔負。”

以我之見,一致的自動渣滓回收零碎無法適用於各種不同的使用環境,而又不至於招致完成上的擔負。稍後我將設計一個針對特定類型的可選的渣滓回收器,可以很分明地看到,或多或少總是存在一些效率上的開支,假如強迫C++用戶必需承受這一點,也許是不可取的。

關於為什麼C++沒有渣滓回收以及能夠的在C++中為此做出的努力,下面提到的著作是我所看過的對這個問題敘說的最片面的,雖然只要短短的一個大節的內容,但是曾經涵蓋了很多內容,這正是Bjarne著作的一向特點,言簡意赅而內韻十足。

上面一步一步地向大家引見我自己土制佳釀的渣滓回收零碎,可以依照需求自在選用,而不影響其他代碼。

結構函數和析構函數

C++中提供的結構函數和析構函數很好的處理了自動釋放資源的需求。Bjarne有一句名言,“資源需求就是初始化(Resource Inquirment Is Initialization)”。

因而,我們可以將需求分配的資源在結構函數中請求完成,而在析構函數中釋放曾經分配的資源,只需對象的生活期完畢,對象懇求分配的資源即被自動釋放。

那麼就僅剩下一個問題了,假如對象自身是在自在存儲區(Free Store,也就是所謂的“堆”)中靜態創立的,並由指針管理(置信你曾經知道為什麼了),則還是必需經過編碼顯式的調用析構函數,當然是借助指針的delete表達式。

智能指針

僥幸的是,出於某些緣由,C++的規范庫中至多引入了一品種型的智能指針,雖然在運用上有局限性,但是它剛好可以處理我們的這個難題,這就是規范庫中獨一的一個智能指針::std::auto_ptr<>。

它將指針包裝成了類,並且重載了反援用(dereference)運算符operator *和成員選擇運算符operator ->,以模擬指針的行為。關於auto_ptr<>的詳細細節,參閱《The C++ Standard Library》(中譯本:C++規范庫)。

例如以下代碼,

#include < cstring >
#include < memory >
#include < iostream >


class string
{
public:
  string(const char* cstr) { _data=new char [ strlen(cstr)+1 ]; strcpy(_data, cstr); }
  ~string() { delete [] _data; }
  const char* c_str() const { return _data; }
private:
  char* _data;
};


void foo()
{
  ::std::auto_ptr < string > str ( new string( " hello " ) );
  ::std::cout << str->c_str() << ::std::endl;
}

由於str是函數的部分對象,因而在函數加入點生活期完畢,此時auto_ptr<string>的析構函數調用,自動銷毀外部指針維護的string對象(先前在結構函數中經過new表達式分配而來的),並進而執行string的析構函數,釋放為實踐的字符串靜態請求的內存。在string中也能夠管理其他類型的資源,如用於多線程環境下的同步資源。下圖闡明了下面的進程。

進入函數foo 加入函數
| A
V |
auto_ptr<string>::auto<string>() auto_ptr<string>::~auto_ptr<string>()
| A
V |
string::string() string::~string()
| A
V |
_data=new char[] delete [] _data
| A
V |
運用資源 -----------------------------------> 釋放資源

如今我們擁有了最復雜的渣滓回收機制(我隱瞞了一點,在string中,你依然需求自己編碼控制對象的靜態創立和銷毀,但是這種狀況下的原則極端復雜,就是在結構函數中分配資源,在析構函數中釋放資源,就仿佛飛機駕駛員必需在降落後和下降前反省起落架一樣。),即便在foo函數中發作了異常,str的生活期也會完畢,C++保證自然加入時發作的一切在異常發作時一樣會無效。

auto_ptr<>只是智能指針的一種,它的復制行為提供了一切權轉移的語義,即智能指針在復制時將對外部維護的實踐指針的一切權停止了轉移,例如

auto_ptr < string > str1( new string( < str1 > ) );
cout << str1->c_str();
auto_ptr < string > str2(str1); // str1外部指針不再指向原來的對象
cout << str2->c_str();
cout << str1->c_str(); // 未定義,str1外部指針不再無效

某些時分,需求共享同一個對象,此時auto_ptr就不敷運用,由於某些歷史的緣由,C++的規范庫中並沒有提供其他方式的智能指針,窮途末路了嗎?

另一種智能指針

但是我們可以自己制造另一種方式的智能指針,也就是具有值復制語義的,並且共享值的智能指針。

需求同一個類的多個對象同時擁有一個對象的拷貝時,我們可以運用援用計數(Reference Counting/Using Counting)來完成,已經這是一個C++中為了進步效率與COW(copy on write,改寫時復制)技術一同被普遍運用的技術,後來證明在多線程使用中,COW為了保證行為的正確反而招致了效率降低(Herb Shutter的在C++ Report雜志中的Guru專欄以及整理後出版的《More Exceptional C++》中專門討論了這個問題)。

但是關於我們目前的問題,援用計數自身並不會有太大的問題,由於沒有牽涉到復制問題,為了保證多線程環境下的正確,並不需求過多的效率犧牲,但是為了簡化問題,這裡疏忽了關於多線程平安的思索。

首先我們仿制auto_ptr設計了一個類模板(出自Herb Shutter的《More Execptional C++》),

template < typename T >
class shared_ptr
{
private:
 class implement // 完成類,援用計數
 {
 public:
  implement(T* pp):p(pp),refs(1){}
  
  ~implement(){delete p;}
  
  T* p; // 實踐指針
  size_t refs; // 援用計數
 };
 implement* _impl;


public:
 explicit shared_ptr(T* p)
  : _impl(new implement(p)){}


 ~shared_ptr()
 {
  decrease(); // 計數遞加
 }


 shared_ptr(const shared_ptr& rhs)
  : _impl(rhs._impl)
 {
  increase(); // 計數遞增
 }
 
 shared_ptr& operator=(const shared_ptr& rhs)
 {
  if (_impl != rhs._impl) // 防止自賦值
  {
   decrease(); // 計數遞加,不再共享原對象
   _impl=rhs._impl; // 共享新的對象
   increase(); // 計數遞增,維護正確的援用計數值
  }
  return *this;
 }


 T* operator->() const
 {
  return _impl->p;
 }
  
 T& operator*() const
 {
  return *(_impl->p);
 }
 
private:
 void decrease()
 {
  if (--(_impl->refs)==0)
  { // 不再被共享,銷毀對象
   delete _impl;
  }
 }
 
 void increase()
 {
  ++(_impl->refs);
 }
};

這個類模板是如此的復雜,所以都不需求對代碼停止太多地闡明。這裡僅僅給出一個復雜的運用實例,足以闡明shared_ptr<>作為復雜的渣滓回收器的替代品。

void foo1(shared_ptr < int >& val)
{
 shared_ptr < int > temp(val);
 *temp=300;
}


void foo2(shared_ptr < int >& val)
{
 val=shared_ptr < int > ( new int(200) );
}


int main()
{
 shared_ptr < int > val(new int(100));
 cout<<"val="<<*val;
 foo1(val); 
 cout<<"val="<<*val;
 foo2(val);
 cout<<"val="<<*val;
}

在main()函數中,先調用foo1(val),函數中運用了一個部分對象temp,它和val共享同一份數據,並修正了實踐值,函數前往後,val擁有的值異樣也發作了變化,而實踐上val自身並沒有修正過。

然後調用了foo2(val),函數中運用了一個無名的暫時對象創立了一個新值,運用賦值表達式修正了val,同時val和暫時對象擁有同一個值,函數前往時,val依然擁有這正確的值。

最後,在整個進程中,除了在運用shared_ptr < int >的結構函數時運用了new表達式創立新之外,並沒有任何刪除指針的舉措,但是一切的內存管理均正確無誤,這就是得益於shared_ptr<>的精巧的設計。

擁有了auto_ptr<>和shared_ptr<>兩大利器當前,應該足以應付大少數狀況下的渣滓回收了,假如你需求更復雜語義(次要是指復制時的語義)的智能指針,可以參考boost的源代碼,其中設計了多品種型的智能指針。

規范容器

關於需求在順序中擁有相反類型的多個對象,善用規范庫提供的各種容器類,可以最大限制的根絕顯式的內存管理,但是規范容器並不適用於貯存指針,這樣關於多態性的支持依然面臨窘境。

運用智能指針作為容器的元素類型,但是規范容器和算法大少數需求值復制語義的元素,後面引見的轉移一切權的auto_ptr和自制的共享對象的shared_ptr都不能提供正確的值復制語義,Herb Sutter在《More Execptional C++》中設計了一個具有完全復制語義的智能指針ValuePtr,處理了指針用於規范容器的問題。

但是,多態性依然沒有處理,我將在另一篇文章專門引見運用容器管理多態對象的問題。

言語支持

為什麼不在C++言語中添加對渣滓回收的支持?

依據後面的討論,我們可以看見,不同的使用環境,也許需求不同的渣滓回收器,不論三七二十一運用渣滓回收,需求將這些不同類型的渣滓回收器整合在一同,即便可以成功(對此我感到疑心),也會招致效率本錢的添加。

這違背了C++的設計哲學,“不為不用要的功用領取代價”,強迫用戶承受渣滓回收的代價並不可取。

相反,按需選擇你自己需求的渣滓回收器,需求掌握的規則與顯式的管理內存相比,復雜的多,也不容易出錯。

最關鍵的一點, C++並不是“傻瓜型”的編程言語,他喜愛喜歡和藹於考慮的編程者,設計一個適宜自己需求的渣滓回收器,正是對喜歡C++的順序員的一種應戰。

以上就是為大家帶來的關於C++為什麼不參加渣滓回收機制解析全部內容了,希望大家多多支持~

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved