第十七章 Tools for Large Programs 用于大型程序的工具
這一章值得仔細(xì)品味。
17.1 異常處理(Exception Handling)
C++異常處理包括:
1. throw 表達(dá)式(throw expressions), throw 引發(fā)了異常條件。a throw raises an exceptional condition.
基本形式:
// first check that data is for the same item
if (!item1.same_isbn(item2))
throw runtime_error("Data must refer to same ISBN");
|
2. try程序塊try blocks,
基本形式:
while (cin >> item1 >> item2) {
try {
// execute code that will add the two Sales_items
// if the addition fails, the code throws a runtime_error exception
} catch (runtime_error err) {
// remind the user that ISBN must match and prompt for another pair
cout << err.what()
<< ""nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (cin && c == 'n')
break; // break out of the while loop
}
}
|
3. 由標(biāo)準(zhǔn)庫(kù)定義的異常類集合。這些異常類用于在throw和相關(guān)的catch這件傳遞錯(cuò)誤信息。A set of exception classes defined by the librarywhich are used to pass the information about an error between a throw and an associated catch.
17.1.1. 拋出類類型的異常Throwing an Exception of Class
異常是由拋出對(duì)象引起的。對(duì)象的類型決定哪一個(gè)句柄會(huì)被調(diào)用。An exception is raised by throwing an object. The type of that object determines which handler will be invoked.
拋出異常和捕捉異常和實(shí)參如何傳遞給函數(shù)類似。異常是一個(gè)對(duì)象,這個(gè)對(duì)象可以傳遞給非引用的形參,因此異常的了類類型必須是可以拷貝的類類型。Exceptions are thrown and caught in ways that are similar to how arguments are passed to functions. An exception can be an object of any type that can be passed to a nonreference parameter, meaning that it must be possible to copy objects of that type.
當(dāng)執(zhí)行throw的時(shí)候,throw后面的語(yǔ)句就不會(huì)被執(zhí)行。控制權(quán)由throw轉(zhuǎn)移到匹配的catch。When a throw is executed, the statement(s) following the throw are not executed. Instead, control is transferred from the throw to the matching catch.
throw表達(dá)式初始化一個(gè)特殊對(duì)象,這個(gè)對(duì)象稱為異常對(duì)象(exception object)。例如:runtime_error就是一種異常對(duì)象。Instead, the throw expression is used to initialize a special object referred to as the exception object.
異常與指針Exceptions and Pointers
拋出指針總是壞主意。拋出指針要求指針?biāo)赶虻膶?duì)象在對(duì)應(yīng)處理句柄地方一定是存在的。Throwing a pointer requires that the object to which the pointer points exist wherever the corresponding handler resides.
17.1.2. 棧展開(kāi)Stack Unwinding
為局部對(duì)象調(diào)用析構(gòu)函數(shù)Destructors Are Called for Local Objects
棧展開(kāi)期間,局部對(duì)象使用的內(nèi)存被釋放,類類型的局部對(duì)象的析構(gòu)函數(shù)被調(diào)用。During stack unwinding, the memory used by local objects is freed and destructors for local objects of class type are run.
但是在棧展開(kāi)期間,資源不會(huì)被釋放。什么是資源?例如通過(guò)調(diào)用new動(dòng)態(tài)分配內(nèi)存。如果程序塊是因?yàn)?/span>exception退出,編譯器是不會(huì)刪除指針的。這樣分配的內(nèi)存也就不會(huì)被釋放。a block might dynamically allocate memory through a call to new. If the block exits due to an exception, the compiler does not delete the pointer. The allocated memory will not be freed.
析構(gòu)函數(shù)不能拋出異常Destructors Should Never throw Exceptions
棧展開(kāi)期間,執(zhí)行析構(gòu)函數(shù),異常已經(jīng)產(chǎn)生,但是還沒(méi)有處理。Destructors are often executed during stack unwinding. When destructors are executing, the exception has been raised but not yet handled.
如果在棧展開(kāi)期間析構(gòu)函數(shù)也拋出自己的異常,這個(gè)異常不會(huì)被處理,只會(huì)導(dǎo)致調(diào)用terminate函數(shù)。terminate函數(shù)會(huì)調(diào)用abort函數(shù),強(qiáng)制退出。while stack unwinding is in progress for an exception, a destructor that throws another exception of its own that it does not also handle, causes the library terminate function is called. Ordinarily, terminate calls abort, forcing an abnormal exit from the entire program.
結(jié)論:讓析構(gòu)函數(shù)做任何會(huì)引發(fā)異常的事情,都是壞主意。it is usually a very bad idea for a destructor to do anything that might cause an exception.
析構(gòu)函數(shù)和構(gòu)造函數(shù)
構(gòu)造函數(shù)是可以引發(fā)異常的。構(gòu)造函數(shù)引發(fā)異常時(shí),對(duì)象可能已經(jīng)部分被構(gòu)造。即使對(duì)象已經(jīng)被部分構(gòu)造了,我們也必須保證已經(jīng)構(gòu)造的成員能夠被撤銷(xiāo)。Even if the object is only partially constructed, we are guaranteed that the constructed members will be properly destroyed.
未捕獲的異常終止程序Uncaught Exceptions Terminate the Program
如果沒(méi)有匹配的catch,程序就會(huì)調(diào)用terminate函數(shù)。If no matching catch is found, then the program calls the library terminate function.
但是這種情況在Java里是不可能發(fā)生的,因?yàn)榫幾g的時(shí)候就已經(jīng)翹了。
17.1.3. 捕獲異常(Catching an Exception)
異常說(shuō)明符Exception Specifiers在catch語(yǔ)句里就像是包含一個(gè)形參的形參列表。
發(fā)現(xiàn)匹配的異常處理句柄(Finding a Matching Handler)
拋出異常的類型和catch說(shuō)明符(exception specifier)必須完全匹配。只允許以下幾種轉(zhuǎn)換:
- 從非const到const的轉(zhuǎn)換。拋出的非const對(duì)象異常可以匹配接受const引用的catch。Conversions from nonconst to const are allowed. That is, a throw of a nonconst object can match a catch specified to take a const reference.
- 從派生類到基類的轉(zhuǎn)換。Conversions from derived type to base type are allowed.
- 數(shù)組轉(zhuǎn)換為指向數(shù)組類型的指針;函數(shù)轉(zhuǎn)換為指向函數(shù)類型的指針。An array is converted to a pointer to the type of the array; a function is converted to the appropriate pointer to function type.
異常說(shuō)明符Exception Specifiers
異常說(shuō)明符就是catch語(yǔ)句后面緊跟的形參。它可以是一般的對(duì)象,也可以是引用。
catch(exception &e) { //粉色部分就是異常說(shuō)明符
// do cleanup
// print a message
cerr << "Exiting: " << e.what() << endl;
size_t status_indicator = 42; // set and return an
return(status_indicator); // error indicator
}
|
異常說(shuō)明符和繼承
基類的異常說(shuō)明符可以用來(lái)捕捉其派生類型的對(duì)象。an exception specifier for a base class can be used to catch an exception object of a derived type.
異常說(shuō)明符的靜態(tài)類型決定了catch語(yǔ)句可以執(zhí)行的操作。the static type of the exception specifier determines the actions that the catch clause may perform.這實(shí)際上意味,如果異常對(duì)象是派生類,但是catch語(yǔ)句的形參是基類類型,那么異常對(duì)象中的派生類部分就不能使用。
所以,通常,處理繼承相關(guān)的異常類型的catch語(yǔ)句應(yīng)該定義引用類型的形參。Usually, a catch clause that handles an exception of a type related by inheritance ought to define its parameter as a reference.
catch語(yǔ)句的順序必須反映類型的層次關(guān)系
多個(gè)具有繼承關(guān)系的catch語(yǔ)句應(yīng)該是按從繼承層級(jí)的最高級(jí)到最低級(jí)的順序進(jìn)行排序。也就是越靠后越接近基類。Multiple catch clauses with types related by inheritance must be ordered from most derived type to least derived.總而言之就是要保證派生類的句柄在基類的catch語(yǔ)句的前面。handlers for a derived type occurs before a catch for its base type.
17.1.4. 重新拋出(Rethrow)
rethrow僅僅就是throw;
雖然rethrow沒(méi)有指定自己的異常,但是一個(gè)異常對(duì)象依然會(huì)沿著調(diào)用鏈向上傳遞。拋出的異常是最初的異常對(duì)象,而不是catch的形參。當(dāng)catch的形參是基類類型時(shí),我們不可能知道rethrow拋出的異常的實(shí)際類型。這個(gè)類型依賴于異常對(duì)象的動(dòng)態(tài)類型,而不是靜態(tài)類型。Although the rethrow does not specify its own exception, an exception object is still passed up the chain. The exception that is thrown is the original exception object, not the catch parameter. When a catch parameter is a base type, then we cannot know the actual type thrown by a rethrow expression. That type depends on the dynamic type of the exception object, not the static type of the catch parameter.
一般情況,catch也許會(huì)修改它的形參。如果,形參修改后,catch重新拋出異常,那么這些修改只有在異常說(shuō)明符是引用時(shí),才會(huì)繼續(xù)傳播。In general, a catch might change its parameter. If, after changing its parameter, the catch rethrows the exception, then those changes will be propagated only if the exception specifier is a reference:
17.1.5. 捕獲所有異常的句柄 The Catch-All Handler
格式:
// matches any exception that might be thrown
catch (...) {
// place our code here
}
|
如果catch(...)和其它的catch語(yǔ)句結(jié)合起來(lái)使用,它必須在最后,否則跟在catch(...)后面的任何catch語(yǔ)句都不會(huì)被匹配執(zhí)行。If a catch(...) is used in combination with other catch clauses, it must be last; otherwise, any catch clause that followed it could never be matched.
17.1.6. 函數(shù)try程序塊與構(gòu)造函數(shù)Function Try Blocks and Constructors
這是為了捕捉構(gòu)造函數(shù)初始化式constructor initializer中的異常。因?yàn)闃?gòu)造函數(shù)的函數(shù)體里的catch語(yǔ)句是不能管理發(fā)生在構(gòu)造函數(shù)初始化式執(zhí)行過(guò)程中的異常的。A catch clause inside the constructor body cannot handle an exception that might occur while processing a constructor initializer.
function try block:
template <class T> Handle<T>::Handle(T *p)
try : ptr(p), use(new size_t(1)) //要把初始化式包含在try程序塊里
{
// empty function body
} catch(const std::bad_alloc &e)
{ handle_out_of_memory(e); }
|
17.1.7. 異常類層次Exception Class Hierarchies
標(biāo)準(zhǔn)庫(kù)異常類。基類excepion,其只是定義了虛函數(shù)what()。
關(guān)注由excepion派生出來(lái)的兩種異常類:runtime_error和logic_error。
定義應(yīng)用級(jí)的異常類Exception Classes:
class isbn_mismatch: public std::logic_error {
public:
explicit isbn_mismatch(const std::string &s): std::logic_error(s) { }
isbn_mismatch(const std::string &s,
const std::string &lhs, const std::string &rhs):
std::logic_error(s), left(lhs), right(rhs) { }
const std::string left, right;
virtual ~isbn_mismatch() throw() { } // 解釋見(jiàn):Section 17.1.10
};
|
使用自定義異常:
Sales_item
operator+(const Sales_item& lhs, const Sales_item& rhs)
{
if (!lhs.same_isbn(rhs))
throw isbn_mismatch("isbn mismatch",
lhs.book(), rhs.book());
Sales_item ret(lhs); // copy lhs into a local object that we'll return
ret += rhs; // add in the contents of rhs
return ret; // return ret by value
}
|
17.1.8. 自動(dòng)資源釋放Automatic Resource Deallocation
問(wèn)題提出:
void f()
{
vector<string> v; // local vector
string s;
while (cin >> s)
v.push_back(s); // populate the vector
string *p = new string[v.size()]; // dynamic array
// 如果在這個(gè)地方拋出異常,那么動(dòng)態(tài)分配的數(shù)組所占用的內(nèi)存就無(wú)法釋放
delete [] p;
}
|
用類管理資源分配(Using Classes to Manage Resource Allocation)
簡(jiǎn)單地說(shuō)就是使用定義類封裝資源的獲得和釋放。(define a class to encapsulate the acquisition and release of a resource.)
原型:
class Resource {
public:
Resource(parms p): r(allocate(p)) { }
~Resource() { release(r); }
// also need to define copy and assignment
private:
resource_type *r; // resource managed by this type
resource_type *allocate(parms p); // allocate this resource
void release(resource_type*); // free this resource
};
|
調(diào)用方法:
void fcn()
{
Resource res(args); // allocates resource_type
// code that might throw an exception
...
}
|
結(jié)論就是不要讓動(dòng)態(tài)分配的資源“裸奔”L
17.1.9. auto_ptr 類The auto_ptr Class
auto_ptr 類的限制:
只能管理一個(gè)對(duì)象,不能管理數(shù)組。An auto_ptr may hold only a pointer to an object and may not be used to point to a dynamically allocated array.
對(duì)照來(lái)看安全和不安全的寫(xiě)法:
不安全:
void f()
{
int *ip = new int(42); // dynamically allocate a new object
// 如果此時(shí)拋出異常,ip指向的內(nèi)存不會(huì)被釋放
delete ip; // return the memory before exiting
}
|
安全:
void f()
{
auto_ptr<int> ap(new int(42)); // allocate a new object
// code that throws an exception that is not caught inside f
}
|
定義auto_ptr 對(duì)象:
auto_ptr<int> pi(new int(1024)); //實(shí)參是new表達(dá)式返回的對(duì)象
|
使用auto_ptr (Using an auto_ptr)
由于auto_ptr重載了解引用和箭頭操作符,因此使用auto_ptr和使用一般的指針類似。The auto_ptr class defines overloaded versions of the dereference (*) and arrow (->) operators (Section 14.6, p. 523). Because auto_ptr defines these operators, we can use an auto_ptr in some ways that are similar to using a built-in pointer:
auto_ptr的拷貝和賦值操作都是破壞性的操作
之所以這么說(shuō),是因?yàn)楫?dāng)拷貝一個(gè)auto_ptr或者賦值給令一個(gè)auto_ptr 時(shí),潛在的對(duì)象的所有者就從最初的傳遞給copy結(jié)果一方。最初的auto_ptr 被重置為非綁定狀態(tài)。When we copy an auto_ptr or assign its value to another auto_ptr, ownership of the underlying object is transferred from the original to the copy. The original auto_ptr is reset to an unbound state.
測(cè)試auto_ptr
get方法返回的auto_ptr包含的潛在的指針。
if (p_auto.get())
*p_auto = 1024;
|
Reset操作
不能對(duì)指針進(jìn)行地址賦值,要通過(guò)reset操作來(lái)修改指針。從而是auto_ptr綁定到一個(gè)新的指針上。
p_auto.reset(new int(1024));
|
調(diào)用auto_ptr 的reset,在auto_ptr綁定到另一個(gè)對(duì)象之前,刪除auto_ptr 指向的對(duì)象。(如果有的話)Calling reset on an auto_ptr deletes the object (if any) to which the auto_ptr refers before binding the auto_ptr to another object.
17.1.10. 異常說(shuō)明Exception Specifications
定義異常說(shuō)明
void recoup(int) throw(runtime_error);
|
在編譯的時(shí)候,編譯器不能也不會(huì)試圖驗(yàn)證異常說(shuō)明。The compiler cannot and does not attempt to verify exception specifications at compile time.
異常說(shuō)明和析構(gòu)函數(shù)
標(biāo)準(zhǔn)的exception類希望析構(gòu)函數(shù)不拋出任何的異常。可以參看17.1.2析構(gòu)函數(shù)不能拋出異常。
class isbn_mismatch: public std::logic_error {
public:
virtual ~isbn_mismatch() throw() { }
};
|
虛析構(gòu)函數(shù):即使是基類指針,也會(huì)調(diào)用派生類的析構(gòu)函數(shù)。如果析構(gòu)函數(shù)為虛函數(shù),那么通過(guò)指針調(diào)用時(shí),運(yùn)行哪個(gè)析構(gòu)函數(shù)將因指針?biāo)笇?duì)象類型的不同而不同。(唉,復(fù)習(xí)一下,就是忽然掰不開(kāi)了)
異常說(shuō)明和虛函數(shù)
基類的虛函數(shù)可以有不同于派生類的異常說(shuō)明。然而派生類的虛函數(shù)的異常說(shuō)明必須等于基類的虛函數(shù)的異常說(shuō)明,或者更加嚴(yán)格。A virtual function in a base class may have an exception specification that differs from the exception specification of the corresponding virtual in a derived class. However, the exception specification of a derived-class virtual function must be either equally or more restrictive than the exception specification of the corresponding base-class virtual function.
KAO,直說(shuō)啊,就是派生類不能增加新的異常。
基類:
class Base {
public:
virtual double f1(double) throw ();
virtual int f2(int) throw (std::logic_error);
virtual std::string f3() throw (std::logic_error, std::runtime_error);
};
|
派生類:
class Derived : public Base {
public:
//基類的f1函數(shù)不拋出異常,派生類拋出underflow_error異常,這就叫做派生類的異常說(shuō)明沒(méi)有基類的異常說(shuō)明限制線強(qiáng)(less restrictive),所以錯(cuò)誤!就是說(shuō)派生類不能增加新的異常。
double f1(double) throw (std::underflow_error);
// ok: same exception specification as Base::f2
int f2(int) throw (std::logic_error);
// ok: Derived f3 is more restrictive
std::string f3() throw ();
};
|
基類的異常列表是派生類的虛函數(shù)可能會(huì)拋出異常的超集。Our code can rely on the fact that the list of exceptions in the base class is a superset of the list of exceptions that a derived-class version of the virtual might throw.
17.1.11. 函數(shù)指針的異常說(shuō)明Function Pointer Exception Specifications
異常說(shuō)明可以看做是函數(shù)類型的一部分,因此如果定義函數(shù)指針,這個(gè)函數(shù)指針也要提供異常說(shuō)明的定義:
void (*pf)(int) throw(runtime_error);
|
如果不指定異常說(shuō)明,這種指針就是指向拋出任何類型異常的匹配函數(shù)。If no specification is provided, then the pointer may point at a function with matching type that could throw any kind of exception.
當(dāng)指向帶有異常說(shuō)明的函數(shù)指針由另一個(gè)指針初始化時(shí),兩個(gè)指針的異常說(shuō)明可以不一致。但是源指針的說(shuō)明至少和目標(biāo)指針的顯著性相同。When a pointer to function with an exception specification is initialized from (or assigned to) another pointer (or to the address of a function), the exception specifications of both pointers do not have to be identical. However, the specification of the source pointer must be at least as restrictive as the specification of the destination pointer
void recoup(int) throw(runtime_error);
// ok: recoup is as restrictive as pf1
void (*pf1)(int) throw(runtime_error) = recoup;
// ok: recoup is more restrictive than pf2
void (*pf2)(int) throw(runtime_error, logic_error) = recoup;
// error: recoup is less restrictive than pf3
void (*pf3)(int) throw() = recoup;
// ok: recoup is more restrictive than pf4
void (*pf4)(int) = recoup;
|
把函數(shù)指針的比較關(guān)系列成下表:
logic runtime_error _error -> runtime_error -> throw() 越來(lái)越more restrictive。
recoup
|
pf
|
|
runtime_error
|
runtime_error, logic_error
|
recoup is more restrictive than pf2
|
runtime_error
|
throw()
|
recoup is less restrictive than pf3
|
17.2 命名空間Namespaces
命名空間可以看做是Java中的package的概念嗎?
一個(gè)命名空間就是一個(gè)作用域。A namespace is a scope.
17.2.1. 命名空間的定義Namespace Definitions
定義
注意不能以分號(hào)結(jié)束。
關(guān)鍵字namespace
namespace cplusplus_primer {
class Sales_item { /* ... */};
Sales_item operator+(const Sales_item&,
const Sales_item&);
class Query {
public:
Query(const std::string&);
std::ostream &display(std::ostream&) const;
// ...
};
class Query_base { /* ... */};
}
|
可以出現(xiàn)在命名空間的,包括:
l 類
l 變量
l 函數(shù)
l 模板
l 其它命名空間
以上這些都可以叫做的命名空間成員namespace members。
在命名空間外使用命名空間的成員
namespace_name::member_name
或者
using cplusplus_primer::Query;
命名空間可以是不連續(xù)的定義
這樣可以按管理類和函數(shù)定義的相同的方式來(lái)組織命名空間。a namespace can be organized in the same way that we manage our own class and function definitions
1. 定義類的命名空間成員,以及作為類接口的一部分的函數(shù)聲明與對(duì)象聲明,可以放在頭文件中,使用命名空間成員的文件可以包含這些頭文件。
2. 命名空間成員的定義可以放在單獨(dú)的文件里。
// ---- Sales_item.h ----
namespace cplusplus_primer {
class Sales_item { /* . . . */};
Sales_item operator+(const Sales_item&,
const Sales_item&);
// declarations for remaining functions in the Sales_item interface
}
// ---- Sales_item.cc ----
namespace cplusplus_primer {
// definitions for Sales_item members and overloaded operators
}
|
17.2.2. 嵌套命名空間Nested Namespaces
嵌套命名空間也遵守同樣的規(guī)則,這些規(guī)則包括:
1. 在一個(gè)封裝的命名空間內(nèi)聲明的名字會(huì)被同名的名字隱藏,這個(gè)同名的名字聲明在一個(gè)嵌套的命名空間里。Names declared in an enclosing namespace are hidden by declarations of the same name in a nested namespace.
2. 對(duì)于命名空間來(lái)說(shuō),在其嵌套命名空間里定義的名字是局部的。Names defined inside a nested namespace are local to that namespace.
3. 在封裝的命名空間之外的代碼如果想要引用嵌套命名空間里的名字,則必須通過(guò)限定詞。Code in the outer parts of the enclosing namespace may refer to a name in a nested namespace only through its qualified name.
17.2.3. 未命名的命名空間Unnamed Namespaces
對(duì)于特殊的文件來(lái)說(shuō),未命名的命名空間是局部的,并且也不能跨多個(gè)文本文件。但是在同一個(gè)文件中,它可以是不連續(xù)的。the definition of an unnamed namespace is local to a particular file and never spans multiple text files.
未命名的命名空間一般用來(lái)定義實(shí)體,這些實(shí)體對(duì)于所在的文件來(lái)說(shuō)是局部實(shí)體。定義在未命名的命名空間里的名字只對(duì)包含這個(gè)命名空間的文件可見(jiàn)。Names defined in an unnamed namespace are visible only to the file containing the namespace.
定義在未命名空間里的名字可以在定義這個(gè)未命名空間的命名空間中找到。Names defined in an unnamed namespace are found in the same scope as the scope at which the namespace is defined.
以上這個(gè)規(guī)則就是造成下面的錯(cuò)誤原因:
int i; // global declaration for i
namespace {
int i;
}
// error: ambiguous defined globally and in an unnested, unnamed namespace
i = 10;
|
但是如果這樣:
Namespace local {
int i; // global declaration for i
namespace {
int i;
}
//error在這里引用i是錯(cuò)誤的,引起二義性
i = 10;
}
// 在這里引用I OK
loca:i = 10;
loca::i = 10;
|
17.2.4. 命名空間成員的使用Using Namespace Members
using 聲明的作用域Scope of a using Declaration
名字從using聲明開(kāi)始直到包含該聲明的作用域結(jié)束都是可見(jiàn)的。外部作用域中定義的同名實(shí)體被屏蔽。The name is visible from the point of the using declaration to the end of the scope in which the declaration is found. Entities with the same name defined in an outer scope are hidden.
命名空間別名
很簡(jiǎn)單,就是一個(gè)同義詞:
namespace primer = cplusplus_primer;
namespace Qlib = cplusplus_primer::QueryLib;
|
using 指示(using Directives)
基本形式:
using聲明和using指示區(qū)別是啥?
using聲明將名字直接放入其出現(xiàn)的作用域中。using聲明就好像是命名空間成員的局部別名。A using declaration puts the name directly in the same scope in which the using declaration itself appears. It is as if the using declaration is a local alias for the namespace member.
using指示不是聲明一個(gè)命名空間成員名稱的別名。using指示具有的作用是:提升命名空間成員到最近的作用域。這樣這個(gè)作用域中既包含它自己的命名空間還包含using指示指向的命名空間。A using directive does not declare local aliases for the namespace member names. Rather, it has the effect of lifting the namespace members into the nearest scope that contains both the namespace itself and the using directive.
這個(gè)說(shuō)法很讓人暈。看看后面大師給的example,杠杠的J即使可以看明白,我還是認(rèn)為這些說(shuō)法就如同孔乙己關(guān)于回字的N種寫(xiě)法一樣,沒(méi)有實(shí)用性。
后面一段,大師也是說(shuō)慎用using指示,還是用using聲明好些。
namespace blip {
int bi = 16, bj = 15, bk = 23;
// other declarations
}
int bj = 0; // ok: bj inside blip is hidden inside a namespace
void manip()
{
// using directive - names in blip "added" to global scope
// (提升命名空間成員到最近的作用域)
using namespace blip;
++bi; // sets blip::bi to 17
++bj; // error: ambiguous, global bj or blip::bj?
++::bj; // ok: sets global bj to 1
++blip::bj; // ok: sets blip::bj to 16
int bk = 97; // local bk hides blip::bk
++bk; // sets local bk to 98
}
|
17.2.5. 類、命名空間和作用域Classes, Namespaces, and Scope
尋址方式
名字尋址
當(dāng)尋址一個(gè)名字時(shí),我們向封閉的作用域外尋找。對(duì)于一個(gè)命名空間里的名字,它封閉的作用域或許是一個(gè)或者多個(gè)嵌套命名空間,這些命名空間最終結(jié)束于一個(gè)完全封閉的全局命名空間。只考慮在使用前就已經(jīng)聲明的名字,并且這些名字在塊里依然是可見(jiàn)的。When looking for a name, we look outward through the enclosing scopes. An enclosing scope for a name used inside a namespace might be one or more nested namespaces ending finally with the all-encompassing global namespace. Only names that have been declared before the point of use that are in blocks that are still open are considered
類尋址
當(dāng)一個(gè)名字在類作用域里使用時(shí)間,我們首先在成員自身里尋找,這包括基類。只有找遍類,我們才在封閉的作用域里繼續(xù)尋找。當(dāng)一個(gè)類封裝在一個(gè)作用域里時(shí),還要遵循同樣的規(guī)則:首先是成員,然后是類,然后是在封閉的作用域,一個(gè)或者多個(gè)命名空間。When a name is used in a class scope, we look first in the member itself, then in the class, including any base classes. Only after exhausting the class(es) do we examine the enclosing scopes. When a class is wrapped in a namespace, the same lookup happens: Look first in the member, then the class (including base classes), then look in the enclosing scopes, one or more of which might be a namespace:
依賴于實(shí)參的查找和類類型形參
函數(shù),包括重載操作符,形參是類類型(或者是類類型的指針或引用),并且函數(shù)和形參是定義在同一個(gè)命名空間,當(dāng)類類型的對(duì)象當(dāng)做是實(shí)參時(shí),這樣函數(shù)是可見(jiàn)的。Functions, including overloaded operators, that take parameters of a class type (or pointer or reference to a class type), and that are defined in the same namespace as the class itself, are visible when an object of (or reference or pointer to) the class type is used as an argument.
下面這個(gè)例子getline()無(wú)需顯式調(diào)用std::getline()。因?yàn)樽鳛閷?shí)參的s和getline函數(shù)是在同一個(gè)命名空間里
std::string s;
// ok: calls std::getline(std::istream&, const std::string&)
getline(std::cin, s);
|
隱式友元聲明和命名空間
如果沒(méi)有可見(jiàn)的聲明,那么友元聲明具有把函數(shù)或者類聲明放在作用域的效果。如果類是定義在命名空間里的,那么在同一個(gè)命名空間里, 就會(huì)聲明一個(gè)不同的未聲明的友元函數(shù)。If there isn't a declaration already visible, then the friend declaration has the effect of putting a declaration for that function or class into the surrounding scope. If a class is defined inside a namespace, then an otherwise undeclared friend function is declared in the same namespace
聲明:
namespace A {
class C {
friend void f(const C&); // makes f a member of namespace A
};
}
|
使用:
在這里f函數(shù)調(diào)用時(shí)無(wú)需使用限定詞,是因?yàn)榍懊嫠v的“依賴實(shí)參查找”的規(guī)則。
// f2 defined at global scope
void f2()
{
A::C cobj;
f(cobj); // calls A::f
}
|
17.2.6. 重載與命名空間Overloading and Namespaces
備選函數(shù)和命名空間
命名空間在確定備選函數(shù)上有兩方面的影響:
1. using聲明和using指示可以增加備選函數(shù)。
2. 如果函數(shù)的形參是類類型,那么要在每個(gè)定義了這個(gè)類(或者是這個(gè)類的基類)的命名空間尋找備選函數(shù)。在這些命名空間里的函數(shù)只要名字和調(diào)用的函數(shù)相同都要作為備選函數(shù)。Each namespace that defines a class used as a parameter (and those that define its base class(es)) is searched for candidate functions. Any functions in those namespaces that have the same name as the called function are added to the candidate set.
Example:
namespace NS {
class Item_base { /* ... */ };
void display(const Item_base&) { }
}
class Bulk_item : public NS::Item_base { };
int main() {
Bulk_item book1;
//display的備選函數(shù)還應(yīng)該包括命名空間NS,因?yàn)榛?/span>Item_base是在NS中定義的。
display(book1);
return 0;
}
|
重載和using聲明
如果函數(shù)在命名空間內(nèi)重載,那么using聲明聲明了這個(gè)函數(shù)的所有版本。If a function is overloaded within a namespace, then a using declaration for the name of that function declares all the functions with that name.
為了保證命名空間的接口不沖突,Using聲明合并重載函數(shù)的所有版本。A using declaration incorporates all versions of an overloaded function to ensure that the interface of the namespace is not violated.
在using聲明所在的作用域內(nèi),using聲明引入的函數(shù)重載了其它函數(shù)聲明。The functions introduced by a using declaration overload any other declarations of the functions with the same name already present in the scope where the using declaration appears.
如果using聲明所在作用域內(nèi)已經(jīng)有同名的函數(shù),并且這個(gè)函數(shù)的參數(shù)列表也是相同的,那么這個(gè)using聲明就是錯(cuò)誤的。If the using declaration introduces a function in a scope that already has a function of the same name with the same parameter list, then the using declaration is in error.
重載與 using 指示Overloading and using Directives
跨越多個(gè) using 指示的重載Overloading across Multiple using Directives
如果出現(xiàn)多個(gè)using指示,每個(gè)命名空間的名字都成為候選集合的一部分。If many using directives are present, then the names from each namespace become part of the candidate set.
17.2.7. 命名空間與模板Namespaces and Templates
模板的顯式特化必須在通用模板定義的命名空間內(nèi)聲明。否則特化的名字要和模板的名字不同。An explicit specialization of a template must be declared in the namespace in which the generic template is defined. Otherwise, the specialization would have a different name than the template it specialized.
17.3 Multiple and Virtual Inheritance
17.3.1. 多重繼承Multiple Inheritance
定義:
class Panda : public Bear, public Endangered {
};
|
派生構(gòu)造函數(shù)初始化所有的基類
基類的構(gòu)造函數(shù)的調(diào)用順序是基類出現(xiàn)在類派生列表中的順序。The base-class constructors are invoked in the order in which they appear in the class derivation list.
The order of constructor invocation is not affected by whether the base class appears in the constructor initializer list or the order in which base classes appear within that list.
17.3.2. 轉(zhuǎn)換與多個(gè)基類Conversions and Multiple Base Classes
指向派生類的指針或引用能夠轉(zhuǎn)換成任何基類的指針或引用。A pointer or reference to a derived class can be converted to a pointer or reference to any of its base classes.
使用基類指針不允許訪問(wèn)其它基類的成員。Using a pointer to one base does not allow access to members of another base.
確定使用哪個(gè)虛析構(gòu)函數(shù)
假設(shè)所有的基類都恰當(dāng)?shù)亩x了它們的析構(gòu)函數(shù)是虛函數(shù),那么不管指向的刪除的對(duì)象的指針是何種類型,虛函數(shù)的處理都是一致的。Assuming all the root base classes properly define their destructors as virtual, then the handling of the virtual destructor is consistent regardless of the pointer type through which we delete the object
下面的例子就是對(duì)這段話的最好的說(shuō)明。假設(shè)下面的這些指針都是指向Panda對(duì)象的,析構(gòu)函數(shù)執(zhí)行時(shí),具有完全相同的執(zhí)行順序。Assuming each of these pointers points to a Panda object, the exact same order of destructor invocations occurs in each case.
delete pz; // pz is a ZooAnimal*
delete pb; // pb is a Bear*
delete pp; // pp is a Panda*
delete pe; // pe is a Endangered*
|
17.3.3. 多重繼承派生類的復(fù)制控制
此前的Panda的定義:
class Panda : public Bear, public Endangered {
};
|
因此copy的順序:ZooAnimal, Bear, Endangered.
17.3.4. 多重繼承下的類作用域(Class Scope under Multiple Inheritance)
這部分其實(shí)要說(shuō)明的是如何進(jìn)行名字查找(name lookup)。
在單繼承下,如果在一個(gè)成員函數(shù)中查找一個(gè)名字,先在函數(shù)自身中查找,然后在函數(shù)所在的類中查找,再到基類中查找。
但是在多重繼承下,就會(huì)存在并發(fā)地在多個(gè)基類中查找。
//ying_yang is Panda’s object
ying_yang.print(cout); //error
ying_yang.Endangered::print(cout); //OK.
|
當(dāng)一個(gè)類有多個(gè)基類時(shí),名字查找同時(shí)發(fā)生在所有直接基類。因此就有可能一個(gè)多繼承的派生類從兩個(gè)或者多個(gè)基類中繼承了同名的成員。此時(shí),若不使用限定詞就會(huì)產(chǎn)生二義性。When a class has multiple base classes, name lookup happens simultaneously through all the immediate base classes. It is possible for a multiply derived class to inherit a member with the same name from two or more base classes. Unqualified uses of that name are ambiguous.
并且,即使連個(gè)基類中的函數(shù)同名但是形參列表不一致時(shí),也會(huì)產(chǎn)生二義性。因?yàn)槊植檎铱偸窃谙龋?/span>Name Lookup Happens First)。就是說(shuō)編譯器找到兩個(gè)名字相同的就已經(jīng)翹了。
避免用戶級(jí)的二義性(Avoiding User-Level Ambiguities)
避免潛在二義性的最好的方法是在派生類里定義函數(shù)版本。The best way to avoid potential ambiguities is to define a version of the function in the derived class that resolves the ambiguity.
std::ostream& Panda::print(std::ostream &os) const
{
Bear::print(os); // print the Bear part
Endangered::print(os); // print the Endangered part
return os;
}
|
17.3.5. 虛繼承Virtual Inheritance
在多繼承關(guān)系中,基類有可能在派生類中出現(xiàn)多次。
舉個(gè)例子:istream,ostream是ios的派生類,iostream繼承了istream,ostream,這就意味著iostream包含兩個(gè)ios的對(duì)象。問(wèn)題來(lái)了L這是不可以的。
解決辦法就是虛繼承(virtual inheritance)。
虛繼承是一種機(jī)制:類指定共享它的基類的狀態(tài)。在虛繼承下,不管在派生類的繼承層次關(guān)系中虛基類出現(xiàn)多少次,只共享一個(gè)給定的虛基類的子對(duì)象。共享的基類子對(duì)象稱為虛基類。Virtual inheritance is a mechanism whereby a class specifies that it is willing to share the state of its virtual base class. Under virtual inheritance, only one, shared base-class subobject is inherited for a given virtual base regardless of how many times the class occurs as a virtual base within the derivation hierarchy. The shared base-class subobject is called a virtual base class.
定義:
class istream : public virtual ios { ... };
class ostream : virtual public ios { ... };
// iostream inherits only one copy of its ios base class
class iostream: public istream, public ostream { ... };
|
17.3.6. 虛基類的聲明Virtual Base Class Declaration
虛基類成員的可見(jiàn)性
共享虛基類的成員可以直接、無(wú)二義性的訪問(wèn)。類似地,如果虛基類的一個(gè)成員只沿著一個(gè)派生路徑重新定義,那么重新定義的成員可以直接訪問(wèn)。在非虛派生下,任何一種訪問(wèn)都是具有二義性的。Members in the shared virtual base can be accessed unambiguously and directly. Similarly, if a member from the virtual base is redefined along only one derivation path, then that redefined member can be accessed directly. Under a nonvirtual derivation, both kinds of access would be ambiguous.
17.3.7. 特殊的初始化語(yǔ)義(Special Initialization Semantics)
.因?yàn)槭嵌嘀乩^承,就存在基類被多次初始化的問(wèn)題。因此需要特殊處理。
虛繼承,最末層的派生類(most derived class)的構(gòu)造函數(shù)初始化虛基類。In a virtual derivation, the virtual base is initialized by the most derived constructor.
雖然虛基類是被最末層的派生類初始化的,任何直接或者簡(jiǎn)潔繼承虛基類的類都必須提供自己的基類初始化式。只要?jiǎng)?chuàng)建虛基類的派生類類型的對(duì)象,派生類必須初始化它的虛基類。這些初始化式只有當(dāng)我們初始化中間類型的對(duì)象時(shí)才用到。Although the virtual base is initialized by the most derived class, any classes that inherit immediately or indirectly from the virtual base usually also have to provide their own initializers for that base. As long as we can create independent objects of a type derived from a virtual base, that class must initialize its virtual base. These initializers are used only when we create objects of the intermediate type.
很拗口。看例子就easy to easy。
Bear,Raccoon,Panda都定義構(gòu)造函數(shù)初始化虛基類ZooAnimal。Bear,Raccoon的初始化式只有在創(chuàng)建Bear,Raccoon類型的對(duì)象時(shí)才用到。如果創(chuàng)建Panda對(duì)象,只會(huì)調(diào)用Panda的初始化式。
Bear::Bear(std::string name, bool onExhibit)
: ZooAnimal(name, onExhibit, "Bear") { }
Raccoon::Raccoon(std::string name, bool onExhibit)
: ZooAnimal(name, onExhibit, "Raccoon") { }
Panda::Panda(std::string name, bool onExhibit)
: ZooAnimal(name, onExhibit, "Panda"),
Bear(name, onExhibit),
Raccoon(name, onExhibit),
Endangered(Endangered::critical),
sleeping_flag(false) { }
|
構(gòu)造函數(shù)與析構(gòu)函數(shù)次序
不管虛基類出現(xiàn)在繼承層次關(guān)系的哪部分,虛基類總是優(yōu)先于非虛基類構(gòu)造。Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy.
還是看例子,一目了然,TeddyBear由BookCharacter,Bear,ToyAnimal派生而來(lái)。Bear是虛基類ZooAnimal的派生類,這樣TeddyBear就存在連個(gè)虛基類:ToyAnimal和ZooAnimal。
class Character { /* ... */ };
class BookCharacter : public Character { /* ... */ };
class ToyAnimal { /* ... */ };
class TeddyBear : public BookCharacter,
public Bear, public virtual ToyAnimal
{ /* ... */ };
|
TeddyBear構(gòu)造函數(shù)的調(diào)用次序就是:先虛基類,后非虛基類。
ZooAnimal(); // Bear's virtual base class
ToyAnimal(); // immediate virtual base class
Character(); // BookCharacter's nonvirtual base class
BookCharacter(); // immediate nonvirtual base class
Bear(); // immediate nonvirtual base class
TeddyBear(); // most derived class
|
析構(gòu)函數(shù)相反。