我眼中的C++難點(轉)---smart pointer
1、smart pointer是何方神聖?
smart pointer,嗯,一個好東東,不過它的中文名到底叫什麼至今還是很混亂,有人叫“聰明指標”,有人則稱之為“靈巧指標”,還有人說成“智慧指標”,都不太準確。不過話又說回來就連smart pointer也不是名副其實,因為他並不是一個指標,而是一個特殊的類,不過他能夠模仿C++中的指標,所以大家也就稀裡糊塗地稱之為指標了。這裡,我們就不翻譯了,還叫:smart pointer。
2、smart pointer如何使自己變成一個指標樣子的?
要使smart pointer偽裝成一個指標,有兩個條件必不可少:
1、smart pointer必須是高度型別化的,模板恰好提供了這個功能;
2、smart pointer還要模仿指標主要的兩個運算子->和*,這就要用到運算子過載。
template
class smart_pointer
{
public:
smart_pointer(T* p = 0); //空構造
smart_pointer(const smart_pointer& p);//拷貝構造
~smart_pointer(); //析構,完成自動撤銷
smart_pointer& operator =(smart_pointer& p);//賦值
T& operator*() const {return *the_p;} //模擬*
T* operator->() const {return the_p;} //模擬->
private:
T *the_p; //受保護的指標
};
這裡有一個規則:任何時候,只要你提供了解構函式、拷貝建構函式或賦值運算子中的一個,你通常需要三個都提供。
當然,實際的東西遠比這個複雜,這只是一個大概的樣子,你可以根據具體的應用環境而作相應的變動。
3、smart pointer有那幾種?
到目前為止,C++標準裡面只有一個smart pointer,那就是大家熟知的std::auto_ptr。
靈巧指標是一種外觀和行為都被設計成與內建指標相類似的物件,不過它能提供更多的功能。它們有許多應用的領域,包括資源管理和重複程式碼任務的自動化。
當你使用靈巧指標替代C++的內建指標(也就是dumb pointer),你就能控制下面這些方面的指標的行為:
l 構造和析構。你可以決定建立靈巧指標時應該怎麼做。通常賦給靈巧指標預設值0,避免出現令人頭疼的未初始化的指標。當指向某一物件的最後一個靈巧指標被釋放時,某些靈巧指標被設計成負責刪除它們指向的物件。這樣做對防止資源洩漏很有幫助。
l 拷貝和賦值。你能對拷貝靈巧指標或有靈巧指標參與的賦值操作進行控制。對於某些型別的靈巧指標來說,期望的行為是自動拷貝它們所指向的物件或用對這些物件進行賦值操作,也就是進行deep copy(深層拷貝)。對於其它的一些靈巧指標來說,僅僅拷貝指標本身或對指標進行賦值操作。還有一部分型別的靈巧指標根本就不允許這些操作。無論你認為應該如何去做,靈巧指標始終受你的控制。
l Dereferencing(取出指標所指東西的內容)。當用戶引用被靈巧指標所指的物件,會發生什麼事情呢?你可以自行決定。例如你可以用靈巧指標實現條款M17提到的lazy fetching 方法。
靈巧指標從模板中生成,因為要與內建指標類似,必須是strongly typed(強型別)的;模板引數確定指向物件的型別。大多數靈巧指標模板看起來都象這樣:template //靈巧指標物件模板
class SmartPtr {
public:
SmartPtr(T* realPtr = 0); // 建立一個靈巧指標
// 指向dumb pointer所指的
// 物件。未初始化的指標
// 預設值為0(null)
SmartPtr(const SmartPtr& rhs); // 拷貝一個靈巧指標
~SmartPtr(); // 釋放靈巧指標
// make an assignment to a smart ptr
SmartPtr& operator=(const SmartPtr& rhs);
T* operator->() const; // dereference一個靈巧指標
// 以訪問所指物件的成員
T& operator*() const; // dereference 靈巧指標
private:
T *pointee; // 靈巧指標所指的物件
};
拷貝建構函式和賦值操作符都被展現在這裡。對於靈巧指標類來說,不能允許進行拷貝和賦值操作,它們應該被宣告為private。兩個dereference操作符被宣告為const,是因為dereference一個靈巧指標時不會對其自身進行修改(儘管可以修改指標所指的物件)(這樣,const型的靈巧指標才可以使用這兩個操作符。)。最後,每個指向T物件的靈巧指標包含一個指向T的dumb pointer。這個dumb pointer指向的物件才是靈巧指標指向的真正物件。
進入靈巧指標實現的細節之前,應該研究一下使用者如何使用靈巧指標。考慮一下,存在一個分散式系統(即其上的物件一些在本地,一些在遠端)。相對於訪問遠端物件,訪問本地物件通常總是又簡單而又速度快,因為遠端訪問需要遠端過程呼叫(RPC),或其它一些聯絡遠距離計算機的方法。對於編寫程式程式碼的使用者來說,採用不同的方法分別處理本地物件與遠端物件是一件很煩人的事情。讓所有的物件看起來都位於一個地方會更方便。靈巧指標可以讓程式庫實現這樣的夢想。
template // 指向位於分散式 DB(資料庫)
class DBPtr { // 中物件的靈巧指標模板
public: //
DBPtr(T *realPtr = 0); // 建立靈巧指標,指向
// 由一個本地dumb pointer
// 給出的DB 物件
DBPtr(DataBaseID id); // 建立靈巧指標,
// 指向一個DB物件,
// 具有惟一的DB識別符
... // 其它靈巧指標函式
}; //同上
class Tuple { // 資料庫元組類
public:
...
void displayEditDialog(); // 顯示一個圖形對話方塊,
// 允許使用者編輯元組。
// user to edit the tuple
bool isValid() const; // 返回*this是否通過了
}; // 合法性驗證
// 這個類模板用於在修改T物件時進行日誌登記。
// 有關細節參見下面的敘述:
template
class LogEntry {
public:
LogEntry(const T& objectToBeModified);
~LogEntry();
};
void editTuple(DBPtr& pt)
{
LogEntry entry(*pt); // 為這個編輯操作登記日誌
// 有關細節參見下面的敘述
// 重複顯示編輯對話方塊,直到提供了合法的數值。
do {
pt->displayEditDialog();
} while (pt->isValid() == false);
}
在editTuple中被編輯的元組物理上可以位於本地也可以位於遠端,但是編寫editTuple的程式設計師不用關心這些事情。靈巧指標類隱藏了系統的這些方面。就程式設計師所關心的方面而言,通過靈巧指標物件進行訪問元組,除了如何宣告它們不同外,其行為就像一個內建指標。
注意在editTuple中LogEntry物件的用法。一種更傳統的設計是在呼叫displayEditDialog前開始日誌記錄,呼叫後結束日誌記錄。在這裡使用的方法是讓LogEntry的建構函式啟動日誌記錄,解構函式結束日誌記錄。正如條款M9所解釋的,當面對異常時,讓物件自己開始和結束日誌記錄比顯示地呼叫函式可以使得程式更健壯。而且建立一個LogEntry物件比每次都呼叫開始記錄和結束記錄函式更容易。
正如你所看到的,使用靈巧指標與使用dump pointer沒有很大的差別。這表明了封裝是非常有效的。靈巧指標的使用者可以象使用dumb pointer一樣使用靈巧指標。正如我們將看到的,有時這種替代會更透明化。
l 靈巧指標的構造、賦值和析構
靈巧指標的的構造通常很簡單:找到指向的物件(一般由靈巧指標建構函式的引數給出),讓靈巧指標的內部成員dumb pointer指向它。如果沒有找到物件,把內部指標設為0或發出一個錯誤訊號(可以是丟擲一個異常)。
靈巧指標拷貝建構函式、賦值操作符函式和解構函式的實現由於(所指物件的)所有權的問題所以有些複雜。如果一個靈巧指標擁有它指向的物件,當它被釋放時必須負責刪除這個物件。這裡假設靈巧指標指向的物件是動態分配的。這種假設在靈巧指標中是常見的(有關確定這種假設是真實的方法,參見條款M27)。
看一下標準C++類庫中auto_ptr模板。這如條款M9所解釋的,一個auto_ptr物件是一個指向堆物件的靈巧指標,直到auto_ptr被釋放。auto_ptr的解構函式刪除其指向的物件時,會發生什麼事情呢?auto_ptr模板的實現如下:
template
class auto_ptr {
public:
auto_ptr(T *ptr = 0): pointee(ptr) {}
~auto_ptr() { delete pointee; }
...
private:
T *pointee;
};
假如auto_ptr擁有物件時,它可以正常執行。但是當auto_ptr被拷貝或被賦值時,會發生什麼情況呢?
auto_ptr ptn1(new TreeNode);
auto_ptr ptn2 = ptn1; // 呼叫拷貝建構函式
//會發生什麼情況?
auto_ptr ptn3;
ptn3 = ptn2; // 呼叫 operator=;
// 會發生什麼情況?
如果我們只拷貝內部的dumb pointer,會導致兩個auto_ptr指向一個相同的物件。這是一個災難,因為當釋放quto_ptr時每個auto_ptr都會刪除它們所指的物件。這意味著一個物件會被我們刪除兩次。這種兩次刪除的結果將是不可預測的(通常是災難性的)。
另一種方法是通過呼叫new,建立一個所指物件的新拷貝。這確保了不會有許多指向同一個物件的auto_ptr,但是建立(以後還得釋放)新物件會造成不可接受的效能損耗。並且我們不知道要建立什麼型別的物件,因為auto_ptr物件不用必須指向型別為T的物件,它也可以指向T的派生型別物件。虛擬建構函式(參見條款M25)可能幫助我們解決這個問題,但是好象不能把它們用在auto_ptr這樣的通用類中。
如果quto_ptr禁止拷貝和賦值,就可以消除這個問題,但是採用“當auto_ptr被拷貝和賦值時,物件所有權隨之被傳遞”的方法,是一個更具靈活性的解決方案:
template
class auto_ptr {
public:
...
auto_ptr(auto_ptr& rhs); // 拷貝建構函式
auto_ptr& // 賦值
operator=(auto_ptr& rhs); // 操作符
...
};
template
auto_ptr::auto_ptr(auto_ptr& rhs)
{
pointee = rhs.pointee; // 把*pointee的所有權
// 傳遞到 *this
rhs.pointee = 0; // rhs不再擁有
} // 任何東西
template
auto_ptr& auto_ptr::operator=(auto_ptr& rhs)
{
if (this == &rhs) // 如果這個物件自我賦值
return *this; // 什麼也不要做
delete pointee; // 刪除現在擁有的物件
pointee = rhs.pointee; // 把*pointee的所有權
rhs.pointee = 0; // 從 rhs 傳遞到 *this
return *this;
}
注意賦值操作符在接受新物件的所有權以前必須刪除原來擁有的物件。如果不這樣做,原來擁有的物件將永遠不會被刪除。記住,除了auto_ptr物件,沒有人擁有auto_ptr指向的物件。
因為當呼叫auto_ptr的拷貝建構函式時,物件的所有權被傳遞出去,所以通過傳值方式傳遞auto_ptr物件是一個很糟糕的方法。因為:
// 這個函式通常會導致災難發生
void printTreeNode(ostream& s, auto_ptr p)
{ s << *p; }
int main()
{
auto_ptr ptn(new TreeNode);
...
printTreeNode(cout, ptn); //通過傳值方式傳遞auto_ptr
...
}
當printTreeNode的引數p被初始化時(呼叫auto_ptr的拷貝建構函式),ptn指向物件的所有權被傳遞到給了p。當printTreeNode結束執行後,p離開了作用域,它的解構函式刪除它指向的物件(就是原來ptr指向的物件)。然而ptr已不再指向任何物件(它的dumb pointer是null),所以呼叫printTreeNode以後任何試圖使用它的操作都將產生未定義的行為。只有在你確實想把物件的所有權傳遞給一個臨時的函式引數時,才能通過傳值方式傳遞auto_ptr。這種情況很少見。
這不是說你不能把auto_ptr做為引數傳遞,這隻意味著不能使用傳值的方法。通過const引用傳遞(Pass-by-reference-to-const)的方法是這樣的:
// 這個函式的行為更直觀一些
void printTreeNode(ostream& s,
const auto_ptr& p)
{ s << *p; }
在函式裡,p是一個引用,而不是一個物件,所以不會呼叫拷貝建構函式初始化p。當ptn被傳遞到上面這個printTreeNode時,它還保留著所指物件的所有權,呼叫printTreeNode以後還可以安全地使用ptn。所以通過const引用傳遞auto_ptr可以避免傳值所產生的風險。(“引用傳遞”替代“傳值”的其他原因參見Effective C++條款22)。
在拷貝和賦值中,把物件的所有權從一個靈巧指標傳遞到另一箇中去,這種思想很有趣,而且你可能已經注意到拷貝建構函式和賦值操作符不同尋常的宣告方法同樣也很有趣。這些函式的引數通常會帶有const,但是上面這些函式則沒有。實際上在拷貝和賦值中上述這些函式的程式碼修改了這些引數。也就是說,如果auto_ptr物件被拷貝或做為賦值操作的資料來源,就會修改auto_ptr物件!
是的,就是這樣。C++是如此靈活能讓你這樣去做,真是太好了。如果語言要求拷貝建構函式和賦值操作符必須帶有const引數,你必須去掉引數的const屬性(參見Effective C++條款21)或用其他方法實現所有權的轉移。準確地說:當拷貝一個物件或這個物件做為賦值的資料來源,就會修改該物件。這可能有些不直觀,但是它是簡單的、直接的,在這種情況下也是準確的。
如果你發現研究這些auto_ptr成員函式很有趣,你可能希望看看完整的實現。在291頁至294頁上有(指原書頁碼),在那裡你也能看到,在標準C++庫中auto_ptr模板有比這裡所描述的更靈活的拷貝建構函式和賦值操作符。在標準C++庫中,這些函式是成員函式模板,而不只是成員函式。(在本條款的後面會講述成員函式模板。也可以閱讀Effective C++條款25)。
靈巧指標的解構函式通常是這樣的:
template
SmartPtr::~SmartPtr()
{
if (*this owns *pointee) {
delete pointee;
}
}
有時刪除前不需要進行測試,例如在一個auto_ptr總是擁有它指向的物件時。而在另一些時候,測試會更為複雜:一個使用了引用計數(參見條款M29)靈巧指標必須在判斷是否有權刪除所指物件前調整引用計數值。當然還有一些靈巧指標象dumb pointer一樣,當它們被刪除時,對所指物件沒有任何影響。
l 實現Dereference 操作符
讓我們把注意力轉向靈巧指標的核心部分,operator*和operator-> 函式。前者返回所指的物件。理論上,這很簡單:
template
T& SmartPtr::operator*() const
{
perform "smart pointer" processing;
return *pointee;
}
首先,無論函式做什麼,必須先初始化指標或使pointee合法。例如,如果使用lazy fetch(參見條款M17),函式必須為pointee建立一個新物件。一旦pointee合法了,operator*函式就返回其所指物件的一個引用。
注意返回型別是一個引用。如果返回物件,儘管編譯器允許這麼做,卻可能導致災難性後果。必須時刻牢記:pointee不用必須指向T型別物件;它也可以指向T的派生類物件。如果在這種情況下operator*函式返回的是T型別物件而不是派生類物件的引用,你的函式實際上返回的是一個錯誤型別的物件!(這是一個slicing(切割)問題,參見Effective C++條款22和本書條款13。)在返回的這種物件上呼叫虛擬函式,不會觸發與(原先)所指物件的動態型別相符的函式。實際上就是說你的靈巧指標將不能支援虛擬函式,象這樣的指標再靈巧也沒有用。而返回一個引用還能夠具有更高的效率(不需要構造一個臨時物件,參見條款M19)。能夠兼顧正確性與效率當然是一件好事。
如果你是一個急性子的人,你可能會想如果一些人在null靈巧指標上呼叫operator*,也就是說靈巧指標的dumb pointer是null。放鬆。隨便做什麼都行。dereference一個空指標的結果是未定義的,所以隨你怎麼實現都不算錯。想拋一個異常麼?可以,丟擲吧。想呼叫abort函式(可能被assert在失敗時呼叫)?好的,呼叫吧。想遍歷記憶體把每個位元組都設成你生日與256模數麼?當然也可以。雖說這樣做沒有什麼好處,但是就語言本身而言,你完全是自由的。
operator->的情況與operator*是相同的,但是在分析operator->之前,讓我們先回憶一下這個函式呼叫的與眾不同的含義。再考慮editTuple函式,其使用一個指向Tuple物件的靈巧指標:
void editTuple(DBPtr& pt)
{
LogEntry entry(*pt);
do {
pt->displayEditDialog();
} while (pt->isValid() == false);
}
語句
pt->displayEditDialog();
被編譯器解釋為:
(pt.operator->())->displayEditDialog();
這意味著不論operator->返回什麼,它必須在返回結果上使用member-selection operator(成員選擇操作符)(->)。因此operator->僅能返回兩種東西:一個指向某物件的dumb pointer或另一個靈巧指標。多數情況下,你想返回一個普通dumb pointer。在此情況下,你這樣實現operator-> :
template
T* SmartPtr::operator->() const
{
perform "smart pointer" processing;
return pointee;
}
這樣做執行良好。因為該函式返回一個指標,通過operator->呼叫虛擬函式,其行為也是正確的。
對於很多程式來說,這就是你需要了解靈巧指標的全部東西。條款M29的引用計數程式碼並沒有比這裡更多的功能。但是如果你想更深入地瞭解靈巧指標,你必須知道更多的有關dumb pointer的知識和靈巧指標如何能或不能進行模擬dumb pointer。如果你的座右銘是“Most people stop at the Z-but not me(多數人淺嘗而止,但我不能夠這樣) ”,下面講述的內容正適合你
l 測試靈巧指標是否為NULL
目前為止我們討論的函式能讓我們建立、釋放、拷貝、賦值、dereference靈巧指標。但是有一件我們做不到的事情是“發現靈巧指標為NULL”:
SmartPtr ptn;
...
if (ptn == 0) ... // error!
if (ptn) ... // error!
if (!ptn) ... // error!
這是一個嚴重的限制。
在靈巧指標類里加入一個isNull成員函式是一件很容易的事,但是沒有解決當測試NULL時靈巧指標的行為與dumb pointer不相似的問題。另一種方法是提供隱式型別轉換操作符,允許編譯上述的測試。一般應用於這種目的的型別轉換是void* :
template
class SmartPtr {
public:
...
operator void*(); // 如果靈巧指標為null,
... // 返回0, 否則返回
}; // 非0。
SmartPtr ptn;
...
if (ptn == 0) ... // 現在正確
if (ptn) ... // 也正確
if (!ptn) ... // 正確
這與iostream類中提供的型別轉換相同,所以可以這樣編寫程式碼:
ifstream inputFile("datafile.dat");
if (inputFile) ... // 測試inputFile是否已經被
// 成功地開啟。
象所有的型別轉換函式一樣,它有一個缺點:在一些情況下雖然大多數程式設計師希望它呼叫失敗,但是函式確實能夠成功地被呼叫(參見條款M5)。特別是它允許靈巧指標與完全不同的型別之間進行比較:
SmartPtr pa;
SmartPtr po;
...
if (pa == po) ... // 這能夠被成功編譯!
即使在SmartPtr 和 SmartPtr之間沒有operator= 函式,也能夠編譯,因為靈巧指標被隱式地轉換為void*指標,而對於內建指標型別有內建的比較函式。這種進行隱式型別轉換的行為特性很危險。(再回看一下條款M5,必須反反覆覆地閱讀,做到耳熟能詳。)
在void*型別轉換方面,也有一些變化。有些設計者採用到const void*的型別轉換,還有一些採取轉換到bool的方法。這些變化都沒有消除混合型別比較的問題。
有一種兩全之策可以提供合理的測試null值的語法形式,同時把不同型別的靈巧指標之間進行比較的可能性降到最低。這就是在靈巧指標類中過載operator!,當且僅當靈巧指標是一個空指標時,operator!返回true:
template
class SmartPtr {
public:
...
bool operator!() const; // 當且僅當靈巧指標是
... // 空值,返回true。
};
使用者程式如下所示:
SmartPtr ptn;
...
if (!ptn) { // 正確
... // ptn 是空值
}
else {
... // ptn不是空值
}
但是這樣就不正確了:
if (ptn == 0) ... // 仍然錯誤
if (ptn) ... // 也是錯誤的
僅在這種情況下會存在不同型別之間進行比較:
SmartPtr pa;
SmartPtr po;
...
if (!pa == !po) ... // 能夠編譯
幸好程式設計師不會經常這樣編寫程式碼。有趣的是,iostream庫的實現除了提供void*隱式的型別轉換,也有operator!函式,不過這兩個函式通常測試的流狀態有些不同。(在C++類庫標準中(參見Effective C++ 條款49和本書條款M35),void*隱式的型別轉換已經被bool型別的轉換所替代,operator bool總是返回與operator!相反的值。)
l 把靈巧指標轉變成dumb指標
有時你要在一個程式裡或已經使用dumb指標的程式庫中新增靈巧指標。例如,你的分散式資料庫系統原來不是分散式的,所以可能有一些老式的庫函式沒有使用靈巧指標:
class Tuple { ... }; // 同上
void normalize(Tuple *pt); // 把*pt 放入
// 正規化中; 注意使用的
// 是dumb指標
考慮一下,如果你試圖用指向Tuple的靈巧指標作引數呼叫normalize,會出現什麼情況:
DBPtr pt;
...
normalize(pt); // 錯誤!
這種呼叫不能夠編譯,因為不能把DBPtr轉換成Tuple*。你可以這樣做,從而使該該函式正常執行:
normalize(&*pt); // 繁瑣, 但合法
不過我覺得你會討厭這種呼叫方式。
在靈巧指標模板中增加指向T的dumb指標的隱式型別轉換操作符,可以讓以上函式呼叫成功執行:
template // 同上
class DBPtr {
public:
...
operator T*() { return pointee; }
...
};
DBPtr pt;
...
normalize(pt); // 能夠執行
並且這個函式也消除了測試空值的問題:
if (pt == 0) ... // 正確, 把pt轉變成
// Tuple*
if (pt) ... // 同上
if (!pt) ... // 同上 (reprise)
然而,它也有型別轉換函式所具有的缺點(幾乎總是這樣,看條款M5)。它使得使用者能夠很容易地直接訪問dumb指標,繞過了“類指標(pointer-like)”物件所提供的“靈巧”特性:
void processTuple(DBPtr& pt)
{
Tuple *rawTuplePtr = pt; // 把DBPtr 轉變成
// Tuple*
使用raw TuplePtr 修改 tuple;
}
通常,靈巧指標提供的“靈巧”行為特性是設計中的主要組成部分,所以允許使用者使用dumb指標會導致災難性的後果。例如,如果DBPtr實現了條款M29中引用計數的功能,允許客戶端直接對dumb指標進行操作很可能破壞“引用計數”資料結構,而導致引用計數錯誤。
甚至即使你提供一個從靈巧指標到dumb指標的隱式轉換操作符,靈巧指標也不能真正地做到與dumb指標互換。因為從靈巧指標到dumb指標的轉換是“使用者定義型別轉換”,在同一時間編譯器進行這種轉換的次數不能超過一次。例如假設有一個表示所有能夠訪問某一元組的使用者的類:
class TupleAccessors {
public:
TupleAccessors(const Tuple *pt); // pt identifies the
... // tuple whose accessors
}; // we care about
通常,TupleAccessors的單引數建構函式也可以作為從Tuple*到TupleAccessors的型別轉換操作符(參見條款M5)。現在考慮一下用於合併兩個TupleAccessors物件內資訊的函式:
TupleAccessors merge(const TupleAccessors& ta1,
const TupleAccessors& ta2);
因為一個Tuple*可以被隱式地轉換為TupleAccessors,用兩個dumb Tuple*呼叫merge函式,可以正常執行:
Tuple *pt1, *pt2;
...
merge(pt1, pt2); // 正確, 兩個指標被轉換為
// TupleAccessors objects
如果用靈巧指標DBPtr進行呼叫,編譯就會失敗:
DBPtr pt1, pt2;
...
merge(pt1, pt2); // 錯誤!不能把 pt1 和
// pt2轉換稱TupleAccessors物件
因為從DBPtr到TupleAccessors的轉換要呼叫兩次使用者定義型別轉換(一次從DBPtr到Tuple*,一次從Tuple*到TupleAccessors),編譯器不會進行這種序列的轉換。
提供到dumb指標的隱式型別轉換的靈巧指標類也暴露了一個非常有害的bug。考慮這個程式碼:
DBPtr pt = new Tuple;
...
delete pt;
這段程式碼應該不能被編譯,pt不是指標,它是一個物件,你不能刪除一個物件。只有指標才能被刪除,對麼?
當然對了。但是回想一下條款M5:編譯器使用隱式型別轉換來儘可能使函式呼叫成功;再回想一下條款M8:使用delete會呼叫解構函式和operator delete,兩者都是函式。編譯器為了使在delete pt語句裡的兩個函式成功呼叫,就把pt隱式轉換為Tuple*,然後刪除它。(本來是你寫錯了程式碼,而現在卻編譯過了,)這樣做必然會破壞你的程式。
如果pt擁有它指向的物件,物件就會被刪除兩次,一次在呼叫delete時,第二次在pt的解構函式被呼叫時。當pt不擁有物件,而是其它人擁有時,如果擁有者同時負責刪除pt的則情況還好,但是如果擁有者不是負責刪除pt的人,可以預料它以後還會再次刪除該物件。不論是最前者所述的情況還是最後者的情況都會導致一個物件被刪除兩次,這樣做會產生不能預料的後果。
這個bug極為有害,因為隱藏在靈巧指標後面的全部思想就是讓它們不論是在外觀上還是在使用感覺上都與dumb指標儘可能地相似。你越接近這種思想,你的使用者就越可能忘記正在使用靈巧指標。如果他們忘記了正在使用靈巧指標,肯定會在呼叫new之後呼叫delete,以防止資源洩漏,誰又能責備他們這樣做不對呢?
底線很簡單:除非有一個讓人非常信服的原因去這樣做,否則絕對不要提供轉換到dumb指標的隱式型別轉換操作符。
l 靈巧指標和繼承類到基類的型別轉換
假設我們有一個public繼承層次結構,以模型化音樂商店的商品:
class MusicProduct {
public:
MusicProduct(const string& title);
virtual void play() const = 0;
virtual void displayTitle() const = 0;
...
};
class Cassette: public MusicProduct {
public:
Cassette(const string& title);
virtual void play() const;
virtual void displayTitle() const;
...
};
class CD: public MusicProduct {
public:
CD(const string& title);
virtual void play() const;
virtual void displayTitle() const;
...
};
再接著假設,我們有一個函式,給它一個MusicProduct物件,它能顯示產品名,並播放它:
void displayAndPlay(const MusicProduct* pmp, int numTimes)
{
for (int i = 1; i <= numTimes; ++i) {
pmp->displayTitle();
pmp->play();
}
}
這個函式能夠這樣使用:
Cassette *funMusic = new Cassette("Alapalooza");
CD *nightmareMusic = new CD("Disco Hits of the 70s");
displayAndPlay(funMusic, 10);
displayAndPlay(nightmareMusic, 0);
這並沒有什麼值得驚訝的東西,但是當我們用靈巧指標替代dumb指標,會發生什麼呢:
void displayAndPlay(const SmartPtr& pmp,
int numTimes);
SmartPtr funMusic(new Cassette("Alapalooza"));
SmartPtr nightmareMusic(new CD("Disco Hits of the 70s"));
displayAndPlay(funMusic, 10); // 錯誤!
displayAndPlay(nightmareMusic, 0); // 錯誤!
既然靈巧指標這麼聰明,為什麼不能編譯這些程式碼呢?
不能進行編譯的原因是不能把SmartPtr或SmartPtr轉換成SmartPtr。從編譯器的觀點來看,這些類之間沒有任何關係。為什麼編譯器的會這樣認為呢?畢竟SmartPtr 或 SmartPtr不是從SmartPtr繼承過來的,這些類之間沒有繼承關係,我們不可能要求編譯器把一種物件轉換成(完全不同的)另一種型別的物件。
幸運的是,有辦法避開這種限制,這種方法的核心思想(不是實際操作)很簡單:對於可以進行隱式轉換的每個靈巧指標型別都提供一個隱式型別轉換操作符(參見條款M5)。例如在music類層次內,在Cassette和CD的靈巧指標類內你可以加入operator SmartPtr函式:
class SmartPtr {
public:
operator SmartPtr()
{ return SmartPtr(pointee); }
...
private:
Cassette *pointee;
};
class SmartPtr {
public:
operator SmartPtr()
{ return SmartPtr(pointee); }
...
private:
CD *pointee;
};
這種方法有兩個缺點。第一,你必須人為地特化(specialize)SmartPtr類,所以你加入隱式型別轉換操作符也就破壞了模板的通用性。第二,你可能必須新增許多型別轉換符,因為你指向的物件可以位於繼承層次中很深的位置,你必須為直接或間接繼承的每一個基類提供一個型別轉換符。(如果你認為你能夠克服這個缺點,方法是僅僅為轉換到直接基類而提供一個隱式型別轉換符,那麼請再想想這樣做行麼?因為編譯器在同一時間呼叫使用者定義型別轉換函式的次數不能超過一次,它們不能把指向T的靈巧指標轉換為指向T的間接基類的靈巧指標,除非只要一步就能完成。)
如果你能讓編譯器為你編寫所有的型別轉換函式,這會節省很多時間。感謝最近的語言擴充套件,讓你能夠做到,這個擴充套件能宣告(非虛)成員函式模板(通常就叫成員模板(member template)),你能使用它來生成靈巧指標型別轉換函式,如下:
template // 模板類,指向T的
class SmartPtr { // 靈巧指標
public:
SmartPtr(T* realPtr = 0);
T* operator->() const;
T& operator*() const;
template // 模板成員函式
operator SmartPtr() // 為了實現隱式型別轉換.
{
return SmartPtr(pointee);
}
...
};
現在請你注意,這可不是魔術——不過也很接近於魔術。它的原理如下所示。(如果下面的內容讓你感到既冗長又令你費解,請不要失望,一會兒我會給出一個例子。我保證你看完例子後,就能夠更深入地理解這段內容了。)假設編譯器有一個指向T物件的靈巧指標,它要把這個物件轉換成指向“T的基類”的靈巧指標。編譯器首先檢查SmartPtr的類定義,看其有沒有宣告明確的型別轉換符,但是它沒有宣告。(這不是指:在上面的模板沒有宣告型別轉換符。)編譯器然後檢查是否存在一個成員函式模板,並可以被例項化成它所期望的型別轉換。它發現了一個這樣的模板(帶有形式型別引數newType),所以它把newType繫結成T的基類型別來例項化模板。這時,惟一的問題是例項化的成員函式程式碼能否被編譯:傳遞(dumb)指標pointee到指向“T的基類”的靈巧指標的建構函式,必須合法的。指標pointee是指向T型別的,把它轉變成指向其基類(public
或 protected)物件的指標必然是合法的,因此型別轉換操作符能夠被編譯,可以成功地把指向T的靈巧指標隱式地型別轉換為指向“T的基類”的靈巧指標。
舉一個例子會有所幫助。讓我們回到CDs、cassettes、music產品的繼承層次上來。我們先前已經知道下面這段程式碼不能被編譯,因為編譯器不能把指向CD的靈巧指標轉換為指向music產品的靈巧指標:
void displayAndPlay(const SmartPtr& pmp,
int howMany);
SmartPtr funMusic(new Cassette("Alapalooza"));
SmartPtr nightmareMusic(new CD("Disco Hits of the 70s"));
displayAndPlay(funMusic, 10); // 以前是一個錯誤
displayAndPlay(nightmareMusic, 0); // 以前是一個錯誤
修改了靈巧指標類,包含了隱式型別轉換操作符的成員函式模板以後,這個程式碼就可以成功運行了。拿如下呼叫舉例,看看為什麼能夠成功執行:
displayAndPlay(funMusic, 10);
funMusic物件的型別是SmartPtr。函式displayAndPlay期望的引數是SmartPtr地物件。編譯器偵測到型別不匹配,於是尋找把funMusic轉換成SmartPtr物件的方法。它在SmartPtr類裡尋找帶有SmartPtr型別引數的單引數建構函式(參見條款M5),但是沒有找到。然後它們又尋找成員函式模板,以例項化產生這樣的函式。它們在SmartPtr發現了模板,把newType繫結到MusicProduct上,生成了所需的函式。例項化函式,生成這樣的程式碼:
SmartPtr:: operator SmartPtr()
{
return SmartPtr(pointee);
}
能編譯這行程式碼麼?實際上這段程式碼就是用pointee做為引數呼叫SmartPtr的建構函式,所以真正的問題是能否用一個Cassette*指標構造一個SmartPtr物件。現在我們對dumb指標型別之間的轉換已經很熟悉了,Cassette*能夠被傳遞給需要MusicProduct*指標的地方。因此SmartPtr建構函式可以成功呼叫,同樣SmartPtr到 SmartPtr之間的型別轉換也能成功進行。太棒了,實現了靈巧指標之間的型別轉換,還有什麼比這更簡單麼?
此外,還有其它功能麼?不要被這個例子誤導,而認為這種方法只能用於把指標在繼承層次中向上進行型別轉換。這種方法可以成功地用於任何合法的指標型別轉換。如果你有dumb指標T1*和另一種dumb指標T2*,當且僅當你能隱式地把T1*轉換為T2*時,你就能夠隱式地把指向T1的靈巧指標型別轉換為指向T2的靈巧指標型別。
這種技術能給我們幾乎所有想要的行為特性。假設我們用一個新類CasSingle來擴充MusicProduct類層次,用來表示cassette singles。修改後的類層次看起來象這樣:
現在考慮這段程式碼:
template // 同上, 包括作為型別
class SmartPtr { ... }; // 轉換操作符的成員模板
void displayAndPlay(const SmartPtr& pmp,
int howMany);
void displayAndPlay(const SmartPtr& pc,
int howMany);
SmartPtr dumbMusic(new CasSingle("Achy Breaky Heart"));
displayAndPlay(dumbMusic, 1); // 錯誤!
在這個例子裡,displayAndPlay被過載,一個函式帶有SmartPtr 物件引數,其它函式的引數為SmartPtr,我們期望呼叫SmartPtr,因為CasSingle是直接從Cassette上繼承下來的,而間接繼承自MusicProduct。當然這是dumb指標時的工作方法。我們的靈巧指標不會這麼靈巧,它們的轉換操作符是成員函式,對C++編譯器而言,所有型別轉換操作符是同等地位的。因此displayAndPlay的呼叫具有二義性,因為從SmartPtr 到SmartPtr的型別轉換並不比到SmartPtr的型別轉換優先。
通過成員模板來實現靈巧指標的型別轉換還有兩個缺點。第一,支援成員模板的編譯器較少,所以這種技術不具有可移植性。以後情況會有所改觀,但是沒有人知道這會等到什麼時候。第二,這種方法的工作原理不很明瞭,要理解它必須先要深入理解函式呼叫時的引數匹配,隱式型別轉換函式,模板函式隱式例項化和成員函式模板。有些程式設計師以前從來沒有看到過這種技巧,而卻被要求維護使用了這種技巧的程式碼,我真是很可憐他們。這種技巧確實很巧妙,這自然是肯定,但是過於的巧妙可是一件危險的事情。
不要再拐彎抹角了,直接了當地說,我們想要知道的是在繼承類向基類進行型別轉換方面,我們如何能夠讓靈巧指標的行為與dumb指標一樣呢?答案很簡單:不可能。正如Daniel Edelson所說,靈巧指標固然靈巧,但不是指標。最好的方法是使用成員模板生成型別轉換函式,在會產生二義性結果的地方使用casts(型別轉換,參見條款M2)。這不是一個完美的方法,不過已經很不錯了,在一些情況下需去除二義性,所付出的代價與靈巧指標提供複雜的功能相比還是值得的。
l 靈巧指標和const
對於dumb指標來說,const既可以針對指標所指向的東西,也可以針對於指標本身,或者兼有兩者的含義(參見Effective C++條款21):
CD goodCD("Flood");
const CD *p; // p 是一個non-const 指標
//指向 const CD 物件
CD * const p = &goodCD; // p 是一個const 指標
// 指向non-const CD 物件;
// 因為 p 是const, 它
// 必須被初始化
const CD * const p = &goodCD; // p 是一個const 指標
// 指向一個 const CD 物件
我們自然想要讓靈巧指標具有同樣的靈活性。不幸的是只能在一個地方放置const,並只能對指標本身起作用,而不能針對於所指物件:
const SmartPtr p = // p 是一個const 靈巧指標
&goodCD; // 指向 non-const CD 物件
好像有一個簡單的補救方法,就是建立一個指向cosnt CD的靈巧指標:
SmartPtr p = // p 是一個 non-const 靈巧指標
&goodCD; // 指向const CD 物件
現在我們可以建立const和non-const物件和指標的四種不同組合:
SmartPtr p; // non-const 物件
// non-const 指標
SmartPtr p; // const 物件,
// non-const 指標
const SmartPtr p = &goodCD; // non-const 物件
// const指標
const SmartPtr p = &goodCD; // const 物件
// const 指標
但是美中不足的是,使用dumb指標我們能夠用non-const指標初始化const指標,我們也能用指向non-cosnt物件的指標初始化指向const物件的指標;就像進行賦值一樣。例如:
CD *pCD = new CD("Famous Movie Themes");
const CD * pConstCD = pCD; // 正確
但是如果我們試圖把這種方法用在靈巧指標上,情況會怎麼樣呢?
SmartPtr pCD = new CD("Famous Movie Themes");
SmartPtr pConstCD = pCD; // 正確麼?
SmartPtr 與SmartPtr是完全不同的型別。在編譯器看來,它們是毫不相關的,所以沒有理由相信它們是賦值相容的。到目前為止這是一個老問題了,把它們變成賦值相容的唯一方法是你必須提供函式,用來把SmartPtr型別的物件轉換成SmartPtr型別。如果你使用的編譯器支援成員模板,就可以利用前面所說的技巧自動生成你需要的隱式型別轉換操作。(我前面說過,只要對應的dumb指標能進行型別轉換,靈巧指標也就能進行型別轉換,我沒有欺騙你們。帶const的型別轉換也沒有問題。)如果你沒有這樣的編譯器,你必須克服更大的困難。
帶const的型別轉換是單向的:從non-const到const的轉換是安全的,但是從const到non-const則不是安全的。而且用const指標能做的事情,用non-const指標也能做,但是用non-const指標還能做其它一些事情(例如,賦值操作)。同樣,用指向const物件的指標能做的任何事情,用指向non-const物件的指標也能做到,但是用指向non-const物件的指標能夠完成一些指向const物件的指標所不能完成的事情(例如,賦值操作)。
這些規則看起來與public繼承的規則相類似(Effective C++ 條款35)。你能夠把一個派生類物件轉換成基類物件,但是反之則不是這樣,你對基類所做的任何事情對派生類也能做,但是還能對派生類做另外一些事情。我們能夠利用這一點來實現靈巧指標,就是說可以讓每個指向T的靈巧指標類public派生自一個對應的指向const-T的靈巧指標類:
template // 指向const物件的
class SmartPtrToConst { // 靈巧指標
... // 靈巧指標通常的
// 成員函式
protected:
union {
const T* constPointee; // 讓 SmartPtrToConst 訪問
T* pointee; // 讓 SmartPtr 訪問
};
};
template // 指向non-const物件
class SmartPtr: // 的靈巧指標
public SmartPtrToConst {
... // 沒有資料成員
};
使用這種設計方法,指向non-const-T物件的靈巧指標包含一個指向const-T的dumb指標,指向const-T的靈巧指標需要包含一個指向cosnt-T的dumb指標。最方便的方法是把指向const-T的dumb指標放在基類裡,把指向non-const-T的dumb指標放在派生類裡,然而這樣做有些浪費,因為SmartPtr物件包含兩個dumb指標:一個是從SmartPtrToConst繼承來的,一個是SmartPtr自己的。
一種在C世界裡的老式武器可以解決這個問題,這就是union,它在C++中同樣有用。Union在protected中,所以兩個類都可以訪問它,它包含兩個必須的dumb指標型別,SmartPtrToConst物件使用constPointee指標,SmartPtr物件使用pointee指標。因此我們可以在不分配額外空間的情況下,使用兩個不同的指標(參見Effective C++條款10中另外一個例子)這就是union美麗的地方。當然兩個類的成員函式必須約束它們自己僅僅使用適合的指標。這是使用union所冒的風險。
利用這種新設計,我們能夠獲得所要的行為特性:
SmartPtr pCD = new CD("Famous Movie Themes");
SmartPtrToConst pConstCD = pCD; // 正確
l 評價
有關靈巧指標的討論該結束了,在我們離開這個話題之前,應該問這樣一個問題:靈巧指標如此繁瑣麻煩,是否值得使用,特別是如果你的編譯器缺乏支援成員函式模板時。
通常是值得的。例如通過使用靈巧指標極大地簡化了條款M29中的引用計數程式碼。而且正如該例子所顯示的,靈巧指標的使用在一些領域受到極大的限制,例如測試空值、轉換到dumb指標、繼承類向基類轉換和對指向const的指標的支援。同時靈巧指標的實現、理解和維護需要大量的技巧。除錯使用靈巧指標的程式碼也比除錯使用dumb指標的程式碼困難。無論如何你也不可能設計出一種通用目的的靈巧指標,能夠替代dumb指標。
達到同樣的程式碼效果,使用靈巧指標更方便。靈巧指標應該謹慎使用, 不過每個C++程式設計師最終都會發現它們是有用的。
比如C++標準庫裡的std::auto_ptr就是應用很廣的一個例子。它的實現在不同版本的STL中雖有不同,但原理都是一樣,大概是下面這個樣子:
template class auto_ptr
{
public:
typedef X element_type;
explicit auto_ptr(X* p = 0) throw()
: the_p(p) {}
auto_ptr(auto_ptr& a) throw()
: the_p(a.release()) {}
auto_ptr& operator =(auto_ptr& rhs) throw()
{
reset(rhs.release());
return *this;
}
~auto_ptr() throw() {delete the_p;}
X& operator* () const throw() {return *the_p;}
X* operator-> () const throw() {return the_p;}
X* get () const throw() {return the_p;}
X* release() throw()
{
X* tmp = the_p;
the_p = 0;
return tmp;
}
void reset(X* p = 0) throw()
{
if (the_p!=p)
{
delete the_p;
the_p = p;
}
}
private:
X* the_p;
};
關於auto_ptr的使用我不想多說,這不是我們今天的主要話題。它的主要優點是不用delete,可以自動回收已經被分配的空間,由此可以避免資源洩露的問題。很多Java的擁護者經常不分黑白的汙衊C++沒有垃圾回收機制,其實不過是貽笑大方而已。拋開在網上許許多多的商業化和非商業化的C++垃圾回收庫不提,auto_ptr就足以有效地解決這一問題。並且即使在產生異常的情況下,auto_ptr也能正確地回收資源。這對於寫出異常安全(exception-safe)的程式碼具有重要的意義。
那在使用smart pointer的過程中,是否有什麼值得注意的問題呢?
這個問題就太泛泛了,針對不同的smart pointer,有不同的注意事項。比如auto_ptr,您就不能把它用在標準容器裡,因為它只在記憶體中保留一份例項。不過我相信把握我前面說的兩個原則:smart pointer是類而不是指標,是模仿指標,那麼一切問題都好辦。比如,smart pointer作為一個類,那麼以下的做法就可能有問題。
SmartPtr p;
if(p==0)
if(!p)
if(p)
很顯然,p不是一個真正的指標,這麼做可能出錯。而SmartPtr的設計也是很重要的因素。您可以加上一個bool SmartPtr::null() const來進行判斷。如果堅持非要用上面的形式,那也未嘗不可。我們就加上operator void* ()試試:
template class SmartPtr {
public:
...
operator void*() const {return the_p;}
...
private:
T* the_p;
};
這招在basic_ios中就使用過了。這裡也可以更靈活地處理,比如類本身需要operator void*()這樣地操作,那麼上面這招就不靈了。那我們還有過載operator !()等等方法。不怕做不到,只怕想不到。
您能總結一下smart pointer的實質嗎?
smart pointer的實質就是一個外殼,一層包裝。正是多了這層包裝,我們可以做出許多普通指標無法完成的事,比如前面資源自動回收,或者自動進行引用記數,比如ATL中CComPtr和CComQIPtr這兩個COM介面指標類。然而事事都是一把雙刃劍,正由於多了這些功能,又會使smart pointer喪失一些功能。一定切記,畫虎畫皮難畫骨,smart pointer畢竟和真正的指標是大大不同的。
相關推薦
我眼中的C++難點(轉)---smart pointer
1、smart pointer是何方神聖? smart pointer,嗯,一個好東東,不過它的中文名到底叫什麼至今還是很混亂,有人叫“聰明指標”,有人則稱之為“靈巧指標”,還有人說成“智慧指標”,都不太準確。不過話又說回來就連smart pointer也不是名副其實,因
C++中的smart pointer簡單實現
// //a person who can tell us his/her name. // #include<iostream> #include<string> using namespace std; class person{ pu
c++智慧指標(smart pointer)詳解
Smart Pointer Deal with c++11’s smart pointer facility. brief Smart pointers are class objects that behave like built-in
C++ 智慧指標(Smart Pointer)
智慧指標具有非常強大的能力,謹慎而明智的選擇能帶來極大的好處。我不否認智慧指標的能力,雖然我在之前的否認過auto_ptr。可能由於我自身能力的限制,體會不到auto_ptr的好處,但這樣的可能性我覺得已經不大了。但auto_ptr是最簡單的智慧指標,在它的周圍存在大量的作品
Chern大大你好~!偶然機會拜讀你的文章覺得很棒~!也給了小弟很大的激勵,因為我也是半路出家轉從土木轉資工,今年有幸考上資工研究所,目前才剛學完C語言,而對於未來要走的領域有點茫然,想請教您有提到j…
Chern大大你好~!偶然機會拜讀你的文章覺得很棒~!也給了小弟很大的激勵,因為我也是半路出家轉從土木轉資工,今年有幸考上資工研究所,目前才剛學完C語言,而對於未來要走的領域有點茫然,想請教您有提到jserv的系統軟體課程,對於他的課程小弟是滿喜歡的也有在跟著寫作業,未來也想朝這方面前進,但因為他的課程算比較
[我眼中的C#]類的介紹
類的使用 類在使用之前經過定義和宣告兩個階段。類包括資料成員(如欄位,常量,事件等)和函式成員(屬性,方法,索引器,屬性終接器等) 虛方法 父類通過Virtual關鍵字,可以在子類中通過o
【轉】我眼中的IT界offer。。。。。。
我眼中的IT界offer。。。。。。 個人觀點,不喜勿噴。 綜合考慮發展,薪水,環境,壓力。 第0檔:美國網際網路總部special offer(15萬刀起薪) 第一檔: 股份制銀行總行,證券公司,基金公司IT部門(民生,中信,興業,浦
C++中的智慧指標(smart pointer)
指標問題是在學習C++,以及運用C++進行軟體開發過程中經常碰到的問題。其中之一,就是“懸垂指標”。所謂懸垂指標,就是是指指標指向了一塊沒有分配給使用者使用的記憶體,結果未定義,往往導致程式錯誤,而且難以檢測。 用一個簡單的例子來說明懸垂指標: string *sp = n
【C++】智慧指標(Smart Pointer)
1. 傳統指標存在的問題 傳統指標存在諸多的問題,比如指標所指向的物件的生命週期問題,掛起引用(dangling references),以及記憶體洩露(memory leaks). 如下是一個傳統指標的使用過程 void Foo() {
C++11: smart pointer
#include <memory> #include <vector> #include <string> #include <iostream> #include <stdexcept> class
[我眼中的C#]顯式轉換和隱式轉換
上節介紹了15中基礎的變數型別,那麼這些變數之間如何轉換呢?下面我們來介紹一下。 轉換分為顯式轉換和隱式轉換,顯式轉換就是需要我們通過程式碼去控制的使其變數型別發生改變的轉換,而隱式轉換不需要我們去操
[C++] 什麼是智慧指標(Smart Pointer)以及何時使用
答案 1 智慧指標是一個類,它封裝了一個原始的C++指標,以管理所指物件的生命期。沒有單一的智慧指標型別,但所有這些都嘗試以實用的方式抽象原始指標。 智慧指標應優先於原始指標。 如果你覺得你需要使用指標(首先要考慮你是否真的需要指標),你通常會想要使用智慧指
【自己動手】實現簡單的C++ smart pointer
Why Smart Pointer? 為什麼需要智慧指標?因為c++的記憶體管理一直是個令人頭疼的問題。 假如我們有如下person物件:每個person有自己的名字,並且可以告訴大家他叫什麼名字: // //a person who can tell us his/her name. // #inclu
C++深度探索系列:智慧指標(Smart Pointer) [二]
深度探索智慧指標(Smart Pointer) 主題索引: 一、剖析C++標準庫智慧指標(std::auto_ptr) 1.Do you Smart Pointer? 2
C++檔案轉base64字串的程式碼_從網上抄了些_但我做了優化_1G資料從9秒優化到了1秒
程式碼執行效率優化的幾個關鍵點: 1.使用一個編碼陣列, 解決掉編碼中的一些判斷與加減運算 //編碼表 const stati
我眼中的軟件項目實施(一)
項目實施 軟件開發 工作環境 互聯網 軟件測試 隨著互聯網+時代的到來,相信大家對IT軟件已經不再陌生,尤其是企、事業用戶,會涉及到很多大型的數字辦公信息系統,而這些軟件系統項目的實施,需要職業的實施團隊來完成。通俗來講,軟件開發將開發出相應的軟件,解決對應的需求;軟件測試是對已開發的軟件
我眼中Citrix在雲時代的價值與位置 ---- 我的封筆之作
cloud transformation註:文中的很多思想來自於Alan Huang,特此鳴謝。Why Cloud?傳統在剛工作那會,無論是自己所在公司還是國內的企業都還是在上各種業務系統階段,從最基礎的數據庫,中間件,活動目錄,郵件系統到大一些公司上的SAP,CRM系統,可以說10-20年前中國的企業對於I
(原創)我眼中的設計模式系列之簡單工廠模式(一)
int 業務 text 們的 acc 現在 rgs sub reat 簡單工廠模式 在日常的軟件開發中,我們一般都是按照模塊來劃分工作的。 場景一: 試想我們現在有這麽一個模塊,為其他的模塊提供服務,比如說我們調用了好幾個外部接口,統一返回XML字符串,每個接口返回
滬c牌照轉滬牌流程 (夫妻、單身)【上海牌照網】
本地人 怎麽辦 shang .com logs 現在 戶口本 .cn blog 問:2017滬c車牌轉滬牌,對滬c車輛有沒有排放要求? 答:滬c轉大牌,滬c車輛無論是國三排放、國四排放、還是國二排放都沒有要求,只要是滬c牌照都可以轉滬牌的。 問:滬c
【滬c轉滬牌操作流程】滬c怎麽轉滬牌滬c如何轉滬牌?
ron 暫住證 拍照 如果 網址 tar 要求 htm bsp 問: 1、這個月剛剛拍的滬牌額度單,老婆的滬c牌照如何操作轉到我的額度上嗎? 滬c是10年國四的,可以轉嗎? 2、13的福特車子現在是父親的名字下,滬牌額度單是自己的, 可以把父親的車輛過戶到我自己