17.2. Namespaces17.2. 命名空间Every name defined in a given scope must be unique within that scope. This requirement can be difficult to satisfy for large, complex applications. Such applications tend to have many names defined in the global scope. Complex programs composed of independently developed libraries are even more likely to encounter name collisionsthe same name is used in our own code or (more often) in the code supplied to us by independent producers. 在一个给定作用域中定义的每个名字在该作用域中必须是唯一的,对庞大、复杂的应用程序而言,这个要求可能难以满足。这样的应用程序的全局作用域中一般有许多名字定义。由独立开发的库构成的复杂程序更有可能遇到名字冲突——同样的名字既可能在我们自己的代码中使用,也可能(更常见地)在独立供应商提供的代码中使用。 Libraries tend to define a large number of global namesprimarily names of templates, types and functions. When writing an application using libraries from many different vendors, it is almost inevitable that some of these names will clash. This name-clashing problem is known as the namespace pollution problem. 库倾向于定义许多全局名字——主要是模板名、类型名或函数名。在使用来自多个供应商的库编写应用程序的时候,这些名字中有一些几乎不可避免地会发生冲突,这种名字冲突问题称为命名空间污染问题。 Traditionally, programmers avoided namespace pollution by making names of global entities very long, often prefixing the names in their program with specific character sequences: 传统上,程序员通过将全局实体的名字设得很长来避免命名空间污染,经常用特定字符序列作为程序中名字的前缀: class cplusplus_primer_Query { ... }; ifstream& cplusplus_primer_open_file(ifstream&, const string&); This solution is far from ideal: It can be cumbersome for programmers to write and read programs that use such long names. Namespaces provide a much more controlled mechanism for preventing name collisions. Namespaces partition the global namespace, making it easier to use independently produced libraries. A namespace is a scope. By defining a library's names inside a namespace, library authors (and users) can avoid the limitations inherent in global names. 这个解决方案很不理想:程序员编写和阅读使用这种长名字的程序非常麻烦。命名空间为防止名字冲突提供了更加可控的机制,命名空间能够划分全局命名空间,这样使用独立开发的库就更加容易了。一个命名空间是一个作用域,通过在命名空间内部定义库中的名字,库的作者(以及用户)可以避免全局名字固有的限制。 17.2.1. Namespace Definitions17.2.1. 命名空间的定义A namespace definition begins with the keyword namespace followed by the namespace name. 命名空间定义以关键字 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 { /* ... */}; } This code defines a namespace named cplusplus_primer with four members: two classes, an overloaded + operator, and a function. 这段代码定义了名为 cplusplus_primer 的命名空间,它有四个成员:两个类,一个重载的 + 操作符,一个函数。 As with other names, the name of a namespace must be unique within the scope in which the namespace is defined. Namespaces may be defined at global scope or inside another namespace. They may not be defined inside a function or a class. 像其他名字一样,命名空间的名字在定义该命名空间的作用域中必须是唯一的。命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。 Following the namespace name is a block of declarations and definitions delimited by curly braces. Any declaration that can appear at global scope can be put into a namespace: classes, variables (with their initializations), functions (with their definitions), templates, and other namespaces. 命名空间名字后面接着由花括号括住的一块声明和定义,可以在命名空间中放入可以出现在全局作用域的任意声明:类、变量(以及它们的初始化)、函数(以及它们的定义)、模板以及其他命名空间。
Each Namespace Is a Scope每个命名空间是一个作用域The entities defined in a namespace are called namespace members. Just as is the case for any scope, each name in a namespace must refer to a unique entity within that namespace. Because different namespaces introduce different scopes, different namespaces may have members with the same name. 定义在命名空间中的实体称为命名空间成员。像任意作用域的情况一样,命名空间中的每个名字必须引用该命名空间中的唯一实体。因为不同命名空间引入不同作用域,所以不同命名空间可以具有同名成员。 Names defined in a namespace may be accessed directly by other members of the namespace. Code outside the namespace must indicate the namespace in which the name is defined: 在命名空间中定义的名字可以被命名空间中的其他成员直接成员,命名空间外部的代码必须指出名字定义在哪个命名空间中: cplusplus_primer::Query q = cplusplus_primer::Query("hello"); q.display(cout); // ... If another namespace (say, AddisonWesley) also provides a TextQuery class and we want to use that class instead of the one defined in cplusplus_primer, we can do so by modifying our code as follows: 如果另一命名空间(如 AddisonWesley)也提供 TextQuery 类,而且我们想要使用那个类代替 cplusplus_primer 中定义的 TextQuery,可以通过这样修改代码而实现: AddisonWesley::Query q = AddisonWesley::Query("hello"); q.display(cout); // ... Using Namespace Members from Outside the Namespace从命名空间外部使用命名空间成员Of course, always referring to a namespace member using the qualified name 当然,总是使用限定名 namespace_name::member_name can be cumbersome. Just as we've been doing for names defined in the std namespace, we can write a using declaration (Section 3.1, p. 78) to obtain direct access to names we know we'll use frequently: 引用命名空间成员可能非常麻烦。像对 std 中定义的命名空间所做的那样,可以编写 using 声明(第 3.1 节)来获得对我们知道将经常使用的名字的直接访问: using cplusplus_primer::Query; After this using declaration, our program can use the name Query directly without the cplusplus_primer qualifier. We'll see other ways to simplify access in Section 17.2.4 (p. 720). 在这个 using 声明之后,程序可以无须 cplusplus_primer 限定符而直接使用名字 Query,在第 17.2.4 节将介绍简化访问的其他方法。 Namespaces Can Be Discontiguous命名空间可以是不连续的Unlike other scopes, a namespace can be defined in several parts. A namespace is made up of the sum of its separately defined parts; a namespace is cumulative. The separate parts of a namespace can be spread over multiple files. Namespace definitions in different text files are also cumulative. Of course, the usual restriction continues to apply that names are visible only in the files in which they are declared. So, if one part of the namespace requires a name defined in another file, that name must still be declared. 与其他作用域不同,命名空间可以在几个部分中定义。命名空间由它的分离定义部分的总和构成,命名空间是累积的。一个命名空间的分离部分可以分散在多个文件中,在不同文本文件中的命名空间定义也是累积的。当然,名字只在声明名字的文件中可见,这一常规限制继续应用,所以,如果命名空间的一个部分需要定义在另一文件中的名字,仍然必须声明该名字。 Writing a namespace definition 编写命名空间定义: namespace namespace_name { // declarations } either defines a new namespace or adds to an existing one. 既可以定义新的命名空间,也可以添加到现在命名空间中。 If the name namespace_name does not refer to a previously defined namespace, then a new namespace with that name is created. Otherwise, this definition opens an existing namespace and adds these new declarations to that namespace. 如果名字 namespace_name 不是引用前面定义的命名空间,则用该名字创建新的命名空间,否则,这个定义打开一个已存在的命名空间,并将这些新声明加到那个命名空间。 Separation of Interface and Implementation接口和实现的分离The fact that namespace definitions can be discontiguous means that we can compose a namespace from separate interface and implementation files. Thus, a namespace can be organized in the same way that we manage our own class and function definitions: 命名空间定义可以不连续意味着,可以用分离的接口文件和实现文件构成命名空间,因此,可以用与管理自己的类和函数定义相同的方法来组织命名空间:
Organizing our namespaces this way also satisfies the requirement that various entitiesnon-inline functions, static data members, variables, and so forthmay be defined only once in a program. This requirement applies equally to names defined in a namespace. By separating the interface and implementation, we can ensure that the functions and other names we need are defined only once, but the same declaration will be seen whenever the entity is used. 按这种方式组织命名空间,也满足了不同实体(非内联函数、静态数据成员、变量等)只能在一个程序中定义一次的要求,这个要求同样适用于命名空间中定义的名字。通过将接口和实现分离,可以保证函数和其他我们需要的名字只定义一次,但相同的声明可以在任何使用该实体的地方见到。
Defining the Primer Namespace定义本书的命名空间Using this strategy for separating interface and implementation, we might define the cplusplus_primer library in several separate files. The declarations for Sales_item and its related functions that we built in Part I (p. 31) would be placed in Sales_item.h, those for the Query classes of Chapter 15 (p. 557) in Query.h, and so on. The corresponding implementation files would be in files such as Sales_item.cc and Query.cc: 使用将接口和实现分离的策略,可以将 cplusplus_primer 库定义在几个分离的文件中。本书第一部分建立的 Sales_item 的声明及其相关函数可以放在 Sales_item.h 中,第十五章的 Query 类的定义以及相关函数放在 Query.h 中,以此类推。对应的实现文件可以是 Sales_item.cc 和 Query.cc:: // ---- 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 } // ---- Query.h ---- namespace cplusplus_primer { class Query { public: Query(const std::string&); std::ostream &display(std::ostream&) const; // ... }; class Query_base { /* ... */}; } // ---- Sales_item.cc ---- #include "Sales_item.h" namespace cplusplus_primer { // definitions for Sales_item members and overloaded operators } // ---- Query.cc ---- #include "Query.h" namespace cplusplus_primer { // definitions for Query members and related functions } This program organization gives both the developers and users of our library the needed modularity. Each class is still organized into its own interface and implementation files. A user of one class need not compile names related to the others. We can hide the implementations from our users, while allowing the files Sales_item.cc and user.cc to be compiled and linked into one program without causing any compile-time or link-time error. Developers of the library can work independently on the implementation of each type. 这种程序组织给予开发者和库用户必要的模块性。每个类仍组织在自己的接口和实现文件中,一个类的用户不必编译与其他类相关的名字。如果允许 Sales_item.cc 和 user.cc 文件编译和链接到一个程序而不会导致编译时错误和运行时错误,就可以对用户隐藏实现。库的开发者可以独立工作于每个类型的实现。 A program using our library would include whichever headers it needed. The names in those headers are defined inside the cplusplus_primer namespace: 使用我们的库的程序可以包含需要的头文件,那些头文件中的名字定义在命名空间 cplusplus_primer 内部: // ---- user.cc ---- // defines the cplusplus_primer::Sales_item class #include "Sales_item.h" int main() { // ... cplusplus_primer::Sales_item trans1, trans2; // ... return 0; } Defining Namespace Members定义命名空间成员Functions defined inside a namespace may use the short form for names defined in the same namespace: 在命名空间内部定义的函数可以使用同一命名空间中定义的名字的简写形式:
namespace cplusplus_primer {
// members defined inside the namespace may use unqualified names
std::istream&
operator>>(std::istream& in, Sales_item& s)
{
// ...
}
It is also possible to define a namespace member outside its namespace definition. We do so in ways that are similar to defining class members outside a class: The namespace declaration of the name must be in scope, and the definition must specify the namespace to which the name belongs: 也可以在命名空间定义的外部定义命名空间成员,用类似于在类外部定义类成员的方式:名字的命名空间声明必须在作用域中,并且定义必须指定该名字所属的命名空间:
// namespace members defined outside the namespace must use qualified names
cplusplus_primer::Sales_item
cplusplus_primer::operator+(const Sales_item& lhs,
const Sales_item& rhs)
{
Sales_item ret(lhs);
// ...
}
This definition should look similar to class member functions defined outside a class. The return type and function name are qualified by the namespace name. Once the fully qualified function name is seen, we are in the scope of the namespace. Thus, references to namespace members in the parameter list and the function body can use unqualified names to reference Sales_item. 这个定义看起来类似于定义在类外部的类成员函数,返回类型和函数名由命名空间名字限定。一旦看到完全限定的函数名,就处于命名空间的作用域中。因此,形参表和函数体中的命名空间成员引用可以使用非限定名引用 Sales_item。 Members May Not Be Defined in Unrelated Namespaces不能在不相关的命名空间中定义成员Although a namespace member can be defined outside its namespace definition, there are restrictions on where this definition can appear. Only namespaces enclosing the member declaration can contain its definition. For example, operator+ could be defined in either the cplusplus_primer namespace or at global scope. It may not be defined in an unrelated namespace. 虽然可以在命名空间定义的外部定义命名空间成员,对这个定义可以出现的地方仍有些限制,只有包围成员声明的命名空间可以包含成员的定义。例如,operator+ 既可以定义在命名空间 cplusplus_primer 中,也可以定义在全局作用域中,但它不能定义在不相关的命名空间中。 The Global Namespace全局命名空间Names defined at global scopenames declared outside any class, function, or namespaceare defined inside the global namespace. The global namespace is implicitly declared and exists in every program. Each file that defines entities at global scope adds those names to the global namespace. 定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。全局命名空间是隐式声明的,存在于每个程序中。在全局作用域定义实体的每个文件将那些名字加到全局命名空间。 The scope operator can be used to refer to members of the global namespace. Because the global namespace is implicit, it does not have a name; the notation 可以用作用域操作符引用全局命名空间的成员。因为全局命名空间是隐含的,它没有名字,所以记号 ::member_name refers to a member of the global namespace. 引用全局命名空间的成员。
17.2.2. Nested Namespaces17.2.2. 嵌套命名空间A nested namespace is a nested scopeits scope is nested within the namespace that contains it. Names in nested namespaces follow the normal rules: Names declared in an enclosing namespace are hidden by declarations of the same name in a nested namespace. Names defined inside a nested namespace are local to that namespace. Code in the outer parts of the enclosing namespace may refer to a name in a nested namespace only through its qualified name. 一个嵌套命名空间即是一个嵌套作用域——其作用域嵌套在包含它的命名空间内部。嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽。嵌套命名空间内部定义的名字局部于该命名空间。外围命名空间之外的代码只能通过限定名引用嵌套命名空间中的名字。 Nested namespaces can improve the organization of code in a library: 嵌套命名空间可以改进库中代码的组织: namespace cplusplus_primer { // first nested namespace: // defines the Query portion of the library namespace QueryLib { class Query { /* ... */ }; Query operator&(const Query&, const Query&); // ... } // second nested namespace: // defines the Sales_item portion of the library namespace Bookstore { class Item_base { /* ... */ }; class Bulk_item : public Item_base { /* ... */ }; // ... } } The cplusplus_primer namespace now contains two nested namespaces: the namespaces named QueryLib and Bookstore. 命名空间 cplusplus_primer 现在包含两个嵌套命名空间:名为 QueryLib 的命名空间和名为 Bookstore 的命名空间。 Nested namespaces are useful when a library provider needs to prevent names in each part of a library from colliding with names in other parts of the library. 当库提供者需要防止库中每个部分的名字与库中其他部分的名字冲突的时候,嵌套命名空间是很有用的。 The name of a member in a nested namespace is formed from the names of the enclosing namespace(s) and the name of the nested namespace. For example, the name of the class declared in the nested namespace QueryLib is 嵌套命名空间中成员的名字由外围命名空间的名字和嵌套命名空间的名字构成。例如,嵌套命名空间 QueryLib 中声明的类的名字是 cplusplus_primer::QueryLib::Query
17.2.3. Unnamed Namespaces17.2.3. 未命名的命名空间A namespace may be unnamed. An unnamed namespace is a namespace that is defined without a name. An unnamed namespace begins with the keyword namespace. Following the namespace keyword is a block of declarations delimited by curly braces. 命名空间可以是未命名的,未命名的命名空间在定义时没有给定名字。未命名的命名空间以关键字 namespace 开头,接在关键字 namespace 后面的是由花括号定界的声明块。
An unnamed namespace may be discontiguous within a given file but does not span files. Each file has its own unnamed namespace. 未命名的命名空间可以在给定文件中不连续,但不能跨越文件,每个文件有自己的未命名的命名空间。 Unnamed namespaces are used to declare entities that are local to a file. Variables defined in an unnamed namespace are created when the program is started and exist until the program ends. 未命名的命名空间用于声明局部于文件的实体。在未命名的命名空间中定义的变量在程序开始时创建,在程序结束之前一直存在。 Names defined in an unnamed namespace are used directly; after all, there is no namespace name with which to qualify them. It is not possible to use the scope operator to refer to members of unnamed namespaces. 未命名的命名空间中定义的名字可直接使用,毕竟,没有命名空间名字来限定它们。不能使用作用域操作符来引用未命名的命名空间的成员。 Names defined in an unnamed namespace are visible only to the file containing the namespace. If another file contains an unnamed namespace, the namespaces are unrelated. Both unnamed namespaces could define the same name, and the definitions would refer to different entities. 未命名的命名空间中定义的名字只在包含该命名空间的文件中可见。如果另一文件包含一个未命名的命名空间,两个命名空间不相关。两个命名空间可以定义相同的名字,而这些定义将引用不同的实体。 Names defined in an unnamed namespace are found in the same scope as the scope at which the namespace is defined. If an unnamed namespace is defined at the outermost scope in the file, then names in the unnamed namespace must differ from names defined at global scope: 未命名空间中定义的名字可以在定义该命名空间所在的作用域中找到。如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的空间中的名字必须与全局作用域中定义的名字不同: int i; // global declaration for i namespace { int i; } // error: ambiguous defined globally and in an unnested, unnamed namespace i = 10; An unnamed namespace, like any other namespace, may be nested inside another namespace. If the unnamed namespace is nested, then names in it are accessed in the normal way, using the enclosing namespace name(s): 像任意其他命名空间一样,未命名的命名空间也可以嵌套在另一命名空间内部。如果未命名的命名空间是嵌套的,其中的名字按常规方法使用外围命名空间名字访问: namespace local { namespace { int i; } } // ok: i defined in a nested unnamed namespace is distinct from global i local::i = 42;
In all other ways, the members of an unnamed namespace are normal program entities. 在所有其他方式中,未命名的命名空间的成员都是普通程序实体。
17.2.4. Using Namespace Members17.2.4. 命名空间成员的使用Referring to namespace members as namespace_name::member_name is admittedly cumbersome, especially if the namespace name is long. Fortunately, there are ways to make it easier to use namespace members. Our programs have used one of these ways, using declarations (Section 3.1, p. 78). The others, namespace aliases and using directives, will be described in this section. 像命名空间名 namespace_name::member_name 成员名这样引用命名空间的成员无可否认是很麻烦,特别是,命名空间名字很长的时候。幸好,有办法让使用命名空间成员比较容易。我们的程序已经使用了其中的一种方法,就是 using(第 3.1 节),本节将介绍其他方法:命名空间别名和 using 指示。
using Declarations, a Recapusing 声明,扼要重述The programs in this book that use names from the standard library generally assume that an appropriate using declaration has been made: 本书中使用标准库中名字的程序一般假设进行了适当的 using 声明: map<string, vector< pair<size_t, size_t> > > word_map; assumes that the following using declarations have been made: 假定进行了下面的声明: using std::map; using std::pair; using std::size_t; using std::string; using std::vector; A using declaration introduces only one namespace member at a time. It allows us to be very specific regarding which names are used in our programs. 一个 using 声明一次只引入一个命名空间成员,它使得无论程序中使用哪些名字,都能够非常明确。 Scope of a using Declarationusing 声明的作用域Names introduced in a using declaration obey normal scope rules. 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. using 声明中引入的名字遵循常规作用域规则。从 using 声明点开始,直到包含 using 声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。 The shorthand name may be used only within the scope in which it is declared and in scopes nested within that scope. Once the scope ends, the fully qualified name must be used. 简写名字只能在声明它的作用域及其嵌套作用域中使用,一旦该作用域结束了,就必须使用完全限定名。 A using declaration can appear in global, local, or namespace scope. A using declaration in class scope is limited to names defined in a base class of the class being defined. using 声明可以出现在全局作用域、局部作用域或者命名空间作用域中。类作用域中的 using 声明局限于被定义类的基类中定义的名字。 Namespace Aliases命名空间别名A namespace alias can be used to associate a shorter synonym with a namespace name. For example, a long namespace name such as 可用命名空间别名将较短的同义词与命名空间名字相关联。例如,像 namespace cplusplus_primer { /* ... */ }; can be associated with a shorter synonym as follows: 这样的长命名空间名字,可以像下面这样与较短的同义词相关联: namespace primer = cplusplus_primer; A namespace alias declaration begins with the keyword namespace, followed by the (shorter) name of the namespace alias, followed by the = sign, followed by the original namespace name and a semicolon. It is an error if the original namespace name has not already been defined as a namespace. 命名空间别名声明以关键字 namespace 开头,接(较短的)命名空间别名名字,再接 =,再接原来的命名空间名字和分号。如果原来的命名空间名字是未定义的,就会出错。 A namespace alias can also refer to a nested namespace. Rather than writing 命名空间别名也可以引用嵌套的命名空间。除了编写 cplusplus_primer::QueryLib::Query tq; we could define and use an alias for cplusplus_primer::QueryLib: 之外,我们可以定义和使用 cplusplus_primer::QueryLib: 的别名: namespace Qlib = cplusplus_primer::QueryLib; Qlib::Query tq;
using Directivesusing 指示Like a using declaration, a using directive allows us to use the shorthand form of a namespace name. Unlike a using declaration, we retain no control over which names are made visiblethey all are. 像 using 声明一样,using 指示使我们能够使用命名空间名字的简写形式。与 using 声明不同,using 指示无法控制使得哪些名字可见——它们都是可见的。 The Form of a using Directiveusing 指示的形式A using directive begins with the keyword using, followed by the keyword namespace, followed by a namespace name. It is an error if the name is not a previously defined namespace name. using 指示以关键字 using 开头,后接关键字 namespace,再接命名空间名字。如果该名字不是已经定义的命名空间名字,就会出错。 A using directive makes all the names from a specific namespace visible without qualification. The short form names can be used from the point of the using directive to the end of the scope in which the using directive appears. using 指示使得特定命名空间所有名字可见,没有限制。短格式名字可从 using 指示点开始使用,直到出现 using 指示的作用域的末尾。 A using directive may appear in namespace, function, or block scope. It may not appear in a class scope. using 指示使得特定命名空间的所有名字可见,没有限制。短格式名字可从 using 指示点开始使用,直到出现 using 指示的作用域的末尾。
using Directives and Scopeusing 指示与作用域The scope of names introduced by a using directive is more complicated than those for using declarations. 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. Because the declaration is localized, the chance of collisions is minimized. 用 using 指示引入的名字的作用域比 using 声明的更复杂。using 声明将名字直接放入出现 using 声明的作用域,好像 using 声明是命名空间成员的局部别名一样。因为这种声明是局部化的,冲突的机会最小。
In the simplest case, assume we have a namespace A and a function f, both defined at global scope. If f has a using directive for A, then in f it will be as if the names in A appeared in the global scope prior to the definition of f: 在最简单的情况下,假定有命名空间 A 和函数 f,二者都在全局作用域中定义。如果 f 有关于 A 的 using 指示,那么,在 f 中,将好像 A 中的名字出现在全局作用域中 f 的定义之前一样: // namespace A and function f are defined at global scope namespace A { int i, j; } void f() { using namespace A; // injects names from A into the global scope cout << i * j << endl; // uses i and j from namespace A //... }
using Directives Exampleusing 指示例子Let's look at an example: 看一个例子: 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; // clash between ::bj and blip::bj // detected only if bj is used ++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 } The using directive in manip makes all the names in blip directly accessible to manip: The function can refer to the names of these members, using their short form. manip 中的 using 提示使 manip 能够直接访问 blip 中的所有名字:使用它们的简化形式,该函数可以引用这些成员的名字。 The members of blip appear as if they were defined in the scope in which both blip and manip are defined. Given that blip is defined at global scope, then the members of blip appear as if they were declared in global scope. Because the names are in different scopes, local declarations within manip may hide some of the namespace member names. The local variable bk hides the namespace member blip::bk. Referring to bk within manip is not ambiguous; it refers to the local variable bk. blip 的成员看来好像是在定义 blip 和 manip 的作用域中定义的一样。如果在全局作用域中定义 blip,则 blip 的成员看来好像是声明在全局作用域的一样。因为名字在不同的作用域中,manip 内部的局部声明可以屏蔽命名空间的某些成员名字,局部变量 bk 屏蔽命名空间名字 blip::bk,在 manip 内部对 bk 的引用没有二义性,它引用局部变量 bk。 It is possible for names in the namespace to conflict with other names defined in the enclosing scope. For example, the blip member bj appears to manip as if it were declared at global scope. However, there is another object named bj in global scope. Such conflicts are permitted; but to use the name, we must explicitly indicate which version is wanted. Therefore, the use of bj within manip is ambiguous: The name refers both to the global variable and to the member of namespace blip. 命名空间中的名字可能会与外围作用域中定义的其他名字冲突。例如,对 manip 而言,blip 成员 bj 看来好像声明在全局作用域中,但是,全局作用域存在另一名为 bj 的对象。这种冲突是允许的,但为了使用该名字,必须显式指出想要的是哪个版本,因此,在 manip 内部的 bj 使用是有二义性的:该名字既可引用全局变量又可引用命名空间 blip 的成员。 To use a name such as bj, we must use the scope operator to indicate which name is wanted. We would write ::bj to obtain the variable defined in global scope. To use the bj defined in blip, we must use its qualified name, blip::bj. 为了使用像 bj 这样的名字,必须使用作用域操作符指出想要的是哪个名字。可以编写 ::bj 来获得在全局作用域中定义的变量,要使用 blip 中定义的 bj,必须使用它的限定名字 blip::bj。
17.2.5. Classes, Namespaces, and Scope17.2.5. 类、命名空间和作用域As we've noted, namespaces are scopes. As in any other scope, names are visible from the point of their declaration. Names remain visible through any nested scopes until the end of the block in which they were introduced. 正如我们已经注意到的,命名空间是作用域。像在任意其他作用域中一样,名字从声明点开始可见。名字的可见性穿过任意嵌套作用域,直到引入名字的块的末尾。 Name lookup for names used inside a namespace follows the normal C++ lookup rules: 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: 对命名空间内部使用的名字的查找遵循常规 C++ 查找规则:当查找名字的时候,通过外围作用域外查找。对命名空间内部使用的名字而言,外围作用域可能是一个或多个嵌套的命名空间,最终以全包围的全局命名空间结束。只考虑已经在使用点之前声明的名字,而该使用仍在开放的块中: namespace A { int i; namespace B { int i; // hides A::i within B int j; int f1() { int j; // j is local to f1 and hides A::B::j return i; // returns B::i } } // namespace B is closed and names in it are no longer visible int f2() { return j; // error: j is not defined } int j = i; // initialized from A::i } Names used in a class member definition are resolved in much the same way, with one important difference: If the name is not local to the member function, we first try to resolve the name to a class member before looking in the outer scopes. 用非常相似的方式确定类成员定义中使用的名字,只有一个重要区别:如果名字不是局部于成员函数的,就试着在查找更外层作用域之前在类成员中确定名字。 As we saw in Section 12.3 (p. 444), members defined inside a class may use names that appear textually after the definition. For example, a constructor defined inside the class body may initialize the data members even if the declaration of those members appears after the constructor definition. 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: 正如第 12.3 节所介绍的,类内部所定义的成员可以使用出现在定义文本之后的名字。例如,即使数据成员的定义出现在构造函数定义之后,类定义体内部定义的构造函数也可以初始化那些数据成员。当在类作用域中使用名字的时候,首先在成员本身中查找,然后在类中查找,包括任意基类,只有在查找完类之后,才检查外围作用域。当类包在命名空间中的时候,发生相同的查找:首先在成员中找,然后在类(包括基类)中找,再在外围作用域中找,外围作用域中的一个或多个可以是命名空间: namespace A { int i; int k; class C1 { public: C1(): i(0), j(0) { } // ok: initializes C1::i and C1::j int f1() { return k; // returns A::k } int f2() { return h; // error: h is not defined } int f3(); private: int i; // hides A::i within C1 int j; }; int h = i; // initialized from A::i } // member f3 is defined outside class C1 and outside namespace A int A::C1::f3() { return h; // ok: returns A::h } With the exception of member definitions, scopes are always searched upward: A name must be declared before it can be used. Hence, the return in f2 will not compile. It attempts to reference the name h from namespace A, but h has not yet been defined. Had that name been defined in A before the definition of C1, the use of h would be legal. Similarly, the use of h inside f3 is okay, because f3 is defined after A::h has been defined. 除了成员定义例外,总是向上查找作用域:名字在使用之前必须声明。因此,f2 中的 return 语句将不能编译,它试图引用命名空间 A 中的名字 h,但 h 还没有定义。如果使 A 中的名字在 C1 的定义之前定义,h 的使用就是合法的。类似地,f3 内部对 h 的使用是正确的,因为 f3 定义在已经定义了 A::h 之后。
The qualifiers A::C1::f3 indicate the reverse order in which the class scopes and namespace scopes are to be searched. The first scope searched is that of the function f3. Then the class scope of its enclosing class C1 is searched. The scope of the namespace A is searched last before the scope containing the definition of f3 is examined. 限定符 A::C1::f3 指出了查找类作用域和命名空间作用域的相反次序,首先查找函数 f3 的作用域,然后查找外围类 C1 的作用域。在查找包含 f3 定义的作用之前,最后查找命名空间 A 的作用域。 Argument-Dependent Lookup and Class Type Parameters实参相关的查找与类类型形参Consider the following simple program: 考虑下面的简单程序: std::string s; // ok: calls std::getline(std::istream&, const std::string&) getline(std::cin, s); The program uses the std::string type, yet it refers without qualification to the getline function. Why can we use this function without a specific std:: qualifier or a using declaration? 这段程序使用了 std::string 类型,但它不加限制地引用了 getline 函数。为什么可以无须特定 std:: 限定符或 using 声明而使用该函数? It turns out that there is an important exception to the rule that namespace names are hidden. 它给出了屏蔽命名空间名字规则的一个重要例外。
When the compiler sees the use of the getline function 当编译器看到 getline 函数的使用 getline(std::cin, s); it looks for a matching function in the current scope, the scopes enclosing the call to getline, and in the namespace(s) in which the type of cin and the string type are defined. Hence, it looks in the namespace std and finds the getline function defined by the string type. 的时候,它在当前作用域,包含调用的作用域以及定义 cin 的类型和 string 类型的命名空间中查找匹配的函数。因此,它在命名空间 std 中查找并找到由 string 类型定义的 getline 函数。 The reason that functions are made visible if they have a parameter of the class type is to allow nonmember functions that are conceptually part of a class' interface to be used without requiring a separate using declaration. Being able to use nonmember operations is particularly useful for operator functions. 如果函数具有类类型形参就使得函数可见,其原因在于,允许无须单独的 using 声明就可以使用概念上作为类接口组成部分的非成员函数。能够使用非成员操作对操作符函数特别有用。 For example, consider the following simple program: 例如,考虑下面的简单程序: std::string s; cin >> s; In absence of this exception to the lookup rules, we would have to write either: 如果没有查找规则的这个例外,我们将必须编写下面二者之一: using std::operator>>; // need to allow cin >> s std::operator>>(std::cin, s); // ok: explicitly use std::>> Either of these declarations is awkward and would make simple uses of strings and the IO library more complicated. 这两个声明都不方便使用,而且可能使 string 和 IO 库的使用变得更复杂。 Implicit Friend Declarations and Namespaces隐式友元声明与命名空间Recall that when a class declares a friend function (Section 12.5, p. 465), a declaration for the function need not be visible. 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: 回忆一下,当一个类声明友元函数(第 12.5 节)的时候,函数的声明不必是可见的。如果不存在可见的声明,那么,友元声明具有将该函数或类的声明放入外围作用域的效果。如果类在命名空间内部定义,则没有另外声明的友元函数在同一命名空间中声明。 namespace A { class C { friend void f(const C&); // makes f a member of namespace A }; } Because the friend takes an argument of a class type and is implicitly declared in the same namespace as the class, it can be used without using an explicit name-space qualifier: 因为该友元接受类类型实参并与类隐式声明在同一命名空间中,所以使用它时可以无须使用显式命名空间限定符: // f2 defined at global scope void f2() { A::C cobj; f(cobj); // calls A::f } 17.2.6. Overloading and Namespaces17.2.6. 重载与命名空间As we've seen, each namespace maintains its own scope. As a consequence, functions that are members of two distinct namespaces do not overload one another. However, a given namespace can contain a set of overloaded function members. 正如我们所见,每个命名空间维持自己的作用域,因此,作为两个不同命名空间的成员的函数不能互相重载。但是,给定命名空间可以包含一组重载函数成员。 In general, function matching (Section 7.8.2, p. 269) within a namespace happens in the same manner as we've already seen: 一般而言,命名空间内部的函数匹配(第 7.8.2 节)以与我们已经见过的方式相同的方式进行:
Candidate Functions and Namespaces候选函数与命名空间Namespaces can have two impacts on function matching. One of these should be obvious: A using declaration or directive can add functions to the candidate set. The other is much more subtle. 命名空间对函数匹配有两个影响。一个影响是明显的:using 声明或 using 指示可以将函数加到候选集合。另一个影响则微妙得多。 As we saw in the previous section, name lookup for functions that have one or more class-type parameters includes the namespace in which each parameter's class is defined. This rule also impacts how we determine the candidate set. 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. These functions are added even though they otherwise are not visible at the point of the call. Functions with the matching name in those namespaces are added to the candidate set: 正如前节所见,有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间。这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类(以及定义其基类)的每个命名空间,将那些命名空间中任意与被调用函数名字相同的函数加入候选集合。即使这些函数在调用点不可见,也将之加入候选集合。将那些命名空间中带有匹配名字的函数加入候选集合: namespace NS { class Item_base { /* ... */ }; void display(const Item_base&) { } } // Bulk_item's base class is declared in namespace NS class Bulk_item : public NS::Item_base { }; int main() { Bulk_item book1; display(book1); return 0; } The argument, book1, to the display function has class type Bulk_item. The candidate functions for the call to display are not only the functions with declarations that are visible where the function display is called, but also the functions in the namespace where the class Bulk_item and its base class Item_base are declared. The function display(const Item_base&) declared in namespace NS is added to the set of candidate functions. display 函数的实参 book1 具有类类型 Bulk_item。display 调用的候选函数不仅是在调用 display 函数的地方其声明可见的函数,还包括声明 Bulk_item 类及其基类 Item_base 的命名空间中的函数。命名空间 NS 中声明的函数 display(const Item_base&) 被加到候选函数集合中。 Overloading and using Declarations重载与 using 声明A using declaration declares a name. As we saw in Section 15.5.3 (p. 592), there is no way to write a using declaration to refer to a specific function declaration: using 声明声明一个名字。正如第 15.5.3 节所见,没有办法编写 using 声明来引用特定函数声明: using NS::print(int); // error: cannot specify parameter list using NS::print; // ok: using declarations specify names only 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. If there are print functions for int and double in the namespace NS, then a using declaration for NS::print makes both functions visible in the current scope. 如果命名空间内部的函数是重载的,那么,该函数名字的 using 声明声明了所有具有该名字的函数。如果命名空间 NS 中有用于 int 和 double 的函数,则 NS::print 的 using 声明使得两个函数都在当前作用域中可见。 A using declaration incorporates all versions of an overloaded function to ensure that the interface of the namespace is not violated. The author of a library provided different functions for a reason. Allowing users to selectively ignore some but not all of the functions from a set of overloaded functions could lead to surprising program behavior. 一个 using 声明包括重载函数的所有版本以保证不违反命名空间的接口。库作者为一个理由提供不同函数,允许用户选择性地忽略重载函数集合中的某些但不是全部函数,可能会导致奇怪的程序行为。 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 声明引入的函数,重载出现 using 声明的作用域中的任意其他同名函数的声明。 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. Otherwise, the using declaration defines additional overloaded instances of the given name. The effect is to increase the set of candidate functions. 如果 using 声明在已经有同名且带相同形参表的函数的作用域中引入函数,则 using 声明出错,否则,using 定义给定名字的另一重载实例,效果是增大候选函数集合。 Overloading and using Directives重载与 using 指示A using directive lifts the namespace members into the enclosing scope. If a namespace function has the same name as a function declared in the scope at which the namespace is placed, then the namespace member is added to the overload set: using 指示将命名空间成员提升到外围作用域。如果命名空间函数与命名空间所在的作用域中声明的函数同名,就将命名空间成员加到重载集合中: namespace libs_R_us { extern void print(int); extern void print(double); } void print(const std::string &); // using directive: using namespace libs_R_us; // using directive added names to the candidate set for calls to print: // print(int) from libs_R_us // print(double) from libs_R_us // print(const std::string &) declared explicitly void fooBar(int ival) { print("Value: "); // calls global print(const string &) print(ival); // calls libs_R_us::print(int) } Overloading across Multiple using Directives跨越多个 using 指示的重载If many using directives are present, then the names from each namespace become part of the candidate set: 如果存在许多 using 指示,则来自每个命名空间的名字成为候选集合的组成部分: namespace AW { int print(int); } namespace Primer { double print(double); } // using directives: // form an overload set of functions from different namespaces using namespace AW; using namespace Primer; long double print(long double); int main() { print(1); // calls AW::print(int) print(3.1); // calls Primer::print(double) return 0; } The overload set for the function print in global scope contains the functions print(int), print(double), and print(long double). These functions are all part of the overload set considered for the function calls in main, even though these functions were originally declared in different namespace scopes. 全局作用域中 print 函数的重载集合包含函数 print(int)、print(double) 和 print(long double),即使这些函数原来在不同的命名空间中声明,它们都是为 main 中函数调用考虑的重载集合的组成部分。
17.2.7. Namespaces and Templates17.2.7. 命名空间与模板Declaring a template within a namespace impacts how template specializations (Section 16.6, p. 671) are declared: 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. 在命名空间内部声明模板影响着怎样声明模板特化(第 16.6 节):模板的显式特化必须在定义通用模板的命名空间中声明,否则,该特化将与它所特化的模板不同名。 There are two ways to define a specialization: One is to reopen the namespace and add the definition of the specialization, which we can do because namespace definitions are discontiguous, Alternatively, we could define the specialization in the same way that we can define any namespace member outside its namespace definition: by defining the specialization using the template name qualified by the name of the namespace. 有两种定义特化的方式:一种是重新打开命名空间并加入特化的定义,可以这样做是因为命名空间定义是不连续的;或者,可以用与在命名空间定义外部定义命名空间成员相同的方式来定义特化:使用由命名空间名字限定的模板名定义特化。
|