C與C++中的異常處理10 (轉)
中產生的異常:namespace prefix = o ns = "urn:schemas--com::office" />
幾部分來,我一直展示了一些技巧來捕獲從物件的構造中丟擲的異常。這些技巧是在異常從建構函式中漏出來後處理它們。有時,者需要知道這些異常,但通常(如我所採用的例程中)異常是從呼叫者並不關心的私有子物件中爆發的。使得要關心“不可見”的物件表明了設計的脆弱。
在歷史上,(可能拋異常)的建構函式的實現者沒有簡單而健壯的解決方法。看這個簡單的例子:
#include
class buffer
{
public:
explicit buffer(size_t);
~buffer();
private:
char *p;
};
buffer::buffer(size_t const count)
: p(new char[count])
{
}
buffer::~buffer()
{
delete[] p;
}
static void do_something_with(buffer &)
{
}
int main()
{
buffer b(100);
do_something_with(b);
return 0;
}
buffer的建構函式接受字元數目並從自由空間分配,然後初始化buffer::p指向它。如果分配失敗,建構函式中的new語句產生一個異常,而buffer的使用者(這裡是main函式)必須捕獲它。
1.1 try塊
不幸的是,捕獲這個異常不是件容易事。因為丟擲來自buffer::buffer,所有buffer的建構函式的呼叫應該被包在try塊中。沒腦子的解決方法:
try
{
buffer b(count);
}
catch (...)
{
abort();
}
do_something_with(b); // ERROR. At this point,
// 'b' no longer exists
是不行的。do_something_with()的呼叫必須在try塊中:
try
{
buffer b(100);
do_something_with(b);
}
catch (...)
{
abort();
}
//do_something_with(b);
(免得被說閒話:我知道呼叫abort()來處理這個異常有些過份。我只是用它做個示例,因為現在關心的是捕獲異常而不是處理它。)
雖然有些笨拙,但這個方法是有效的。接著考慮這樣的變化:
static buffer b(100);
int main()
{
// buffer b(100);
do_something_with(b);
return 0;
}
現在,b被定義為全域性物件。試圖將它包入try塊
try // um, no, I don't think so
{
static buffer b;
}
catch (...)
{
abort();
}
int main()
{
do_something_with(b);
return 0;
}
將不能被編譯。
1.2 暴露實現
每個例子都顯示了buffer設計上的基本缺陷:buffer的介面以外的實現細節被暴露了。在這裡,暴露的細節是buffer的建構函式中的new語句可能失敗。這個語句用於初始化私有子物件buffer::p――一個main函式和其它使用者不能操作甚至根本不知道的子物件。當然,這些使用者更不應該被要求必須關注這樣的子物件丟擲的異常。
為了改善buffer的設計,我們必須在建構函式中捕獲異常:
#include
class buffer
{
public:
explicit buffer(size_t);
~buffer();
private:
char *p;
};
buffer::buffer(size_t const count)
: p(NULL)
{
try
{
p = new char[count];
}
catch (...)
{
abort();
}
}
buffer::~buffer()
{
delete[] p;
}
static void do_something_with(buffer &)
{
}
int main()
{
buffer b(100);
do_something_with(b);
return 0;
}
異常被包含在建構函式中。使用者,比如main()函式,從不知道異常存在過,世界又一次清靜了。
1.3 常量成員
也這麼做?注意,buffer::p一旦被設定過就不能再被改動。為避免指標被無意改動,謹慎的設計是將它申明為const:
class buffer
{
public:
explicit buffer(size_t);
~buffer();
private:
char * const p;
};
很好,但到了這步時:
buffer::buffer(size_t const count)
{
try
{
p = new char[count]; // ERROR
}
catch (...)
{
abort();
}
}
一旦被初始化,常量成員不能再被改變,即使是在包含它們的物件的建構函式體中。常量成員只能被建構函式的成員初始化列表設定一次。
buffer::buffer(size_t const count)
: p(new char[count]) // OK
這讓我們回到了段落一中,又重新產生了我們最初想解決的問題。
OK,這麼樣如何:不用new語句初始化p,換成用內部使用new的輔助函式來初始化它:
char *new_chars(size_t const count)
{
try
{
return new char[count];
}
catch (...)
{
abort();
}
}
buffer::buffer(int const count)
: p(new_chars(count))
{
// try
// {
// p = new char[count]; // ERROR
// }
// catch (...)
// {
// abort();
// }
}
這個能工作,但代價是一個額外函式卻僅僅用來保護一個幾乎從不發生的事件。
1.4 函式try塊
(WQ注:後面會講到,function try塊不能阻止建構函式的拋異常動作,它其實只起異常過濾的功能!!!見P14.3)
我在上面這些建議中沒有發現哪個能確實令人滿意。我所期望的是一個語言級的解決方案來處理部分構造子物件問題,而又不引起上面說到的問題。幸運的是,語言中恰好包含了這樣一個解決方法。
在深思熟慮後,C++標準委員會增加了一個叫做“function try blocks”的東西到語言規範中。作為try塊的堂兄弟,函式try塊捕獲整個函式定義中的異常,包括成員初始化列表。不用奇怪,因為語言最初沒有被設計了支援函式try塊,所以語法有些怪:
buffer::buffer(size_t const count)
try
: p(new char[count])
{
}
catch
{
abort();
}
看起來想是通常的try塊後面的{}實際上是劃分建構函式的函式體的。在效果上,{}有雙重作用,不然,我們將面對更彆扭的東西:
buffer::buffer(int const count)
try
: p(new char[count])
{
{
}
}
catch
{
abort();
}
(注意:雖然巢狀的{}是多餘的,這個版本能夠編譯。實際上,你可以巢狀任意重{},直到遇到的極限。)
如果在初始化列表中有多個初始化,我們必須將它們放入同一個函式try塊中:
buffer::buffer()
try
: p(...), q(...), r(...)
{
// constructor body
}
catch (std::bad_alloc)
{
// ...
}
和普通的try塊一樣,可以有任意個異常處理函式:
buffer::buffer()
try
: p(...), q(...), r(...)
{
// constructor body
}
catch (std::bad_alloc)
{
// ...
}
catch (int)
{
// ...
}
catch (...)
{
// ...
}
古怪的語法之外,函式try塊解決了我們最初的問題:所有從buffer子物件的建構函式丟擲的異常留在了buffer的建構函式中。
因為我們現在期望buffer的建構函式不丟擲任何異常,我們應該給它一個異常規格申明:
explicit buffer(size_t) throw();
接著一想,我們應該是個更好點的員,於是給我們所有函式加了異常規格申明:
class buffer
{
public:
explicit buffer(size_t) throw();
~buffer() throw();
// ...
};
// ...
static void do_something_with(buffer &) throw()
// ...
Rounding Third and Heading for Home
對我們的例子,最終版本是:
#include
class buffer
{
public:
explicit buffer(size_t) throw();
~buffer() throw();
private:
char *const p;
};
buffer::buffer(size_t const count)
try
: p(new char[count])
{
}
catch (...)
{
abort();
}
buffer::~buffer()
{
delete[] p;
}
static void do_something_with(buffer &) throw()
{
}
int main()
{
buffer b(100);
do_something_with(b);
return 0;
}
用Visual C++編譯,自鳴得意地坐下來,看著的提示輸出。
syntax error : missing ';' before 'try'
syntax error : missing ';' before 'try'
'count' : undeclared identifier
'
to be a function definition
syntax error : missing ';' before 'catch'
syntax error : missing ';' before '{'
missing function header (old-style formal list?)
噢!
Visual C++還不支援函式try塊。在我測試過的編譯器中,只有Edison Design Group C++ Front End version 2.42認為這些程式碼合法。
(順便提一下,我特別關心為什麼編譯將第一個錯誤重複了一下。可能它的計算你第一次會不相信。)
如果你堅持使用Visual C++,你可以使用在介紹函式try塊前所說的解決方法。我喜歡使用額外的new封裝函式。如果你認同,考慮將它做成模板:
template
T *new_array(size_t const count)
{
try
{
return new T[count];
}
catch (...)
{
abort();
}
}
// ...
buffer::buffer(size_t const count)
: p(new_array
{
}
這個模板比原來的new_chars函式通用得多,對char以外的型別也有能工作。同時,它有一個隱蔽的異常相關問題,而我將在下次談到。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992216/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【C++】 C++異常捕捉和處理C++
- C++異常處理機制C++
- Kotlin DSL C++專案引入OpenCV異常處理(轉)KotlinC++OpenCV
- C++整理19_異常處理C++
- C++錯誤和異常處理C++
- 【C++】 63_C語言異常處理C++C語言
- C++ 異常處理機制詳解:輕鬆掌握異常處理技巧C++
- (十五)C++學習 | 強制型別轉換 異常處理C++型別
- C#中的異常處理機制C#
- 關於C++ 的異常處理,解答在這來看看吧~C++
- Java 異常表與異常處理原理Java
- 異常處理機制(二)之異常處理與捕獲
- SpringBoot中異常處理Spring Boot
- C#自定義異常 統一異常處理C#
- C++異常C++
- 29.Spring Boot中異常處理與REST格式處理Spring BootREST
- 瞭解下C# 異常處理C#
- 詳解C#異常處理C#
- Ruby中的TypeError異常處理Error
- C介面與實現—C裡面的異常處理機制
- C#基礎之前處理器,異常處理C#
- 異常的處理
- 《C++ Primer》學習筆記(五):迴圈、分支、跳轉和異常處理語句C++筆記
- 異常-throws的方式處理異常
- 異常篇——異常處理
- [轉載] Java異常處理習題Java
- python異常處理中finally的作用Python
- gRPC 中的異常該如何處理?RPC
- Java 中的異常處理機制Java
- springboot專案中的異常處理Spring Boot
- SpringBoot中的全域性異常處理Spring Boot
- spring中的統一異常處理Spring
- 異常處理
- 異常處理與推導式
- C++語言程式設計筆記 - 第12章 - 異常處理C++程式設計筆記
- 在 C++ 中捕獲 Python 異常C++Python
- JSP 異常處理如何處理?JS
- C++中的字串編碼處理C++字串編碼
- Java中的異常處理最佳實踐Java