Effective C++【三】(Scott Meyers著 侯捷译)

类与函数之设计和声明
只要程序中声明一个新class,便是产生了一个新type,设计良好的classes有自然的语法、直观的语意以及高效的实现。如何设计一个好的高效的class呢,需要面对下边几个问题:
1)、对象应该如何产生和销毁。这个答案强烈影响你的constructor和destructor的设计,以及operator new或new[]或delete或delete[]。
2)、对象的initialization action和assignment action有什么不同。这个答案决定你的constructor行为和assignment运算符的行为以及两者间的差异。
3)、对象以传值(by value)方式传递给新型别是什么意思。记住,copy constructor就是用来定义pass by value的。
4)、对象型别而言,合法值的规范是什么。这些规范决定了你必须在你的member functions内(特别是constructors和assignment运算符内)做哪些错误检测,他会影响函数抛出的exceptions,以及函数的exception specifications。
5)、new type能够塞进某个继承体系之中吗。如果你从既有classes继承下来,你便会受到那些classes设计上的束缚——尤其是你所继承的函数是否为virtual。如果你希望让其他的classes继承你的class,影响所及则是你所声明的函数是否应该是virtual。
6)、哪一种型别转换是允许的呢。如果你希望型别A的对象能够隐式的转换为型别B,那么你必须在class B中为class A写一个型别转换函数,或写一个单引数的non-explicit constructor。如果你只希望在显示转换,那么你必须写一个转换函数,但要避免写“型别转换函数”或“non-explicit且单一引数之constructor”。
7)、什么样的运算符和函数对于新型别是合理的呢。这个答案将决定你为你的class接口声明什么样的函数。
8)、需要明白禁止使用什么样的标准运算符和函数吗。如果有,你应该将他们声明为private(这个够狠够直接)。
9)、谁有权利取用新型别的members?这个答案可帮助你决定所有的members之中哪一个应该是public,哪一个应该是protect或者private。也可以帮忙决定哪个是classes或者friends,或决定是否适合将class设计成另一个class的嵌套类(nested class)。
10)、新型别一般化的程度有多高。或许你并非真正想要定义一个新型别,或许你真正想要的是定义一族群别。如果是这样,那么你不应该定义新的class,而是应该定义一个新的class template。
(ps:书中说上述问题并不好回答,我表示亚历山大,因为基本都没考虑过。 - -!,书中还说展开讨论可以再写一本书,下边要简单说些常见的问题。莫名其妙的又要多看一本书,亚利山巨大)

条款18:努力让接口完满(complete)且最小化
一般而言,接口之中只有函数,因为如果其中也有数据,则会导致许多缺点,见条款20。
所谓complete接口,即允许clients做他们可以做的合理的任何事情(这个有点小体会)。最小化接口就是尽量可能让函数少,不至于出现重复功能的函数,但这个条件是有弹性的,不要那么刻板。
另外operator<<或operator>>以及各种相对关系运算符,条款19会讨论,往往被设计为non-member friend functions而非member functions。friend函数时class接口的一部分。

条款19:区分member functions、non-member functions 和friend functions三者
尝试创建一个分数类:

class rational {
public:
rational(int numerator = 0, int denominator = 1);
const rational operator*(const rational& rhs) const;
int numerator() const;
int denominator() const;
private:
...
};

这样就可以尝试一下做乘法运算了。

rational oneeighth(1, 8);
rational onehalf(1, 2);
rational result = onehalf * oneeighth; // 运行良好
result = result * oneeighth; // 运行良好
result = onehalf * 2; // 运行良好
result = 2 * onehalf; // 出错!
result = onehalf.operator*(2); // 运行良好
result = 2.operator*(onehalf); // 出错!

这里牵扯到编译器如何找到相应的函数的问题。onehalf是一个内含operator*函数的class object,所以编译器调用了该函数,然后整数2没有相应的操作,所以编译器开始在全局范围内(global scope)或某个可见的namespace中搜寻如下形式的non-member operator*:
result=operator*(2,onehalf); //同样错误!
同样会搜寻失败。再看整数2放后边成功的那个,为什么这里的2能够被接受,另一个不可呢?因为这里发生了隐式转换(implicit type conversion)。编译器知道你整传递一个int,但你需要一个rational,并且它也知道只要调用rational的constructor就给予提供的int就可以适当的创造出一个rational,所以 编译器帮你完成了下边的操作:

const rational temp(2); // 从2产生一个临时rational对象
result = onehalf * temp; // 同onehalf.operator*(temp);

当然,只有牵扯到non-explicit constructor,编译器才会这么做。前边写了那么多,其实我主要是为了引出这个explicit constructor概念(因为之前的比较简单,估计大部分人也知道,这个explicit估计会比较少人知道,至少我不太会),这就牵扯到explicit的意义,还以上述程序为例,如果constructor写成:
explicit rational(int numerator = 0, int denominator = 1); 这样子,那么你执行原本运行良好的result = onehalf * 2;也会发生错误。下边了解到一段explicit的特性:
explicit C++ Specific
This keyword is a declaration specifier that can only be applied to in-class constructor declarations. Constructors declared explicit will not be considered for implicit conversions. For example:

class X {
public:
explicit X(int); //legal
explicit X(double) { //legal
// ...
}
};
explicit X::X(int) {} //illegal

An explicit constructor cannot take part in implicit conversions. It can only be used to explicitly construct an object. For example, with the class declared above:

void f(X) {}
void g(int I) {
f(i); // will cause error
}
void h() {
X x1(1); // legal
}

The function call f(i) fails because there is no available implicit conversion from int to X.
Note It is meaningless to apply explicit to constructors with multiple arguments,since such constructors cannot take part in implicit conversions.
END C++ Specific
这样就比较清楚了,explicit主要用于 "修饰 "构造函数. 使得它不能用于程序中需要通过此构造函数进行 "隐式 "转换的情况! 指定此关键字,需要隐式转换方可进行的程序将会不能通过. 而可通过强制转换使它没有用.
事实上,没有explicit关键字,编译器会在任何必要时刻进行隐式转换,不过只有出现于参数表上的参数能享受到这项服务。
这时候我们尝试解决此问题就可以考虑让operator*转成一个non-member function,从而允许编译器在每一个引数身上执行隐式转换:(之前的rational类去掉operator*成员)

const rational operator*(const rational& lhs,const rational& rhs){
return rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}

这样就是一个快乐的解决了。不过还有一点需呀操心,operator*是否应该成为一个rational class的friends呢?本例而言答案是否定了,事实上,能够靠接口实现的就应该尽量避免friend函数。另外还有一例就是operator<<和operator>>的时候,也不适合声明在类中的,可以尝试一下。
如果类中的某些成员是private,并且只能用non-member function实现时,这时候你就需要朋友了。
这条条款可以让大家明白什么时候选择member functions,什么时候选择non-member functions,或者要使用 friend functions。

条款20:避免将data members放在公开接口中
一般有面向对象思想,经常写的人都会这么干吧,这个就不再阐述了。。。

条款21:尽可能使用const
先看一个用法举例:
char *p = "hello"; // 非const指针, 非const数据
const char *p = "hello"; // 非const指针,const数据
char * const p = "hello"; // const指针, 非const数据
const char * const p = "hello"; // const指针 const数据
另外有些人喜欢使用const的时候型别名之前,有些人喜欢放在之后,比如 int const p和const int p,还有这样的:int const *p,const int* p; 其实这是一样的,如果有*号就有所区别了。但依然很好区分:把*号当分界线,在此线左边的就表示指针所指内容为const,如果在右边则是指针是const,两侧都出现就所有都是const。
另外const还能用于修饰函数传回值,参数,整个函数等。详情见另外一篇文章
书中这里说了一个关键词mutable,具体的由一场争论引发出来的,bitwise constness(位常量论)和conceptual constness(概念常量论)。具体意思是前者的缺陷能够通过新申请的一个指针指向常量对象,避开编译器的bitwise constness检测从而改变对象内容。因此就产生了后者conceptual constness,它主张const member function可以改其调用者内的某些位,但只能在不被clients侦测到的情况下,这又引发一个缺陷就是编译器却坚持自己的bitwise constness检测,为此c++标准委员会推出了mutable,此关键词施行于nonstatic data member身上,可以解除bitewise constness束缚。
bitewise constness引发的问题:

class string {
public:
// 构造函数,使data指向一个value所指向的数据的拷贝
string(const char *value);
...
operator char *() const { return data;}
private:
char *data;
};
const string s = "hello"; // 声明常量对象
char *nasty = s; // 调用 operator char*() const
*nasty = 'm'; // 修改s.data[0]
cout << s; // 输出"mello"

另外一个conceptual constness可能引发的问题:
我们希望一个string类可以提供自己的长度。

class string {
public:
// 构造函数,使data指向一个value所指向的数据的拷贝
string(const char *value): lengthisvalid(false) { ... }
...
size_t length() const;
private:
char *data;
size_t datalength; // 最后计算出的string的长度
bool lengthisvalid; // 长度当前是否合法
};
size_t string::length() const{
if (!lengthisvalid) {
datalength = strlen(data); // 错误!之前提到,编译器坚持原则!
lengthisvalid = true; // 错误!同上
}
return datalength;
}

此时在datalenth和lenthisvalid前加关键字mutable即可解出这种束缚。但此关键字是很晚才成为c++标准,有些编译器可能并未支持,这时候你需要做一个常量性的转换const_cast。
ps:书中后边说了3段const_cast可能出错的地方,没太看明白,试验了一下发现没试验出来什么。

条款22:尽量使用pass by reference,少用pass by value
c语言中每样东西都是passed by value,c++承担这项传统的做法是把pass by value当做缺省行为。除非另外指定,负责函数参数都是以实参的副本作为初值,而调用端所获得的亦是函数传回值的一个副本。
给出了一个例子,说明了一个调用一个成员函数如果是传值参数的话,那么就会引发调用很多次constructor和destructor,是很多次,如果类里有类的话,这是几何数的上升。所以尽可能的使用by reference当参数。
另外by reference传值还有一个优点,就是避免所谓的“切割(slicing)问题”,当一个derived class object 被交出去当做一个base class object时,他原本以“成为一个derived class object”的所有特征,都会被slicing掉,只留下base class object。解决此问题的办法就是使用by reference传值。
虽然passing by reference是一件美妙事情,但是会导出某种复杂问题。知名的有aliasing(别名)问题。此外还有些情况无法使用reference。另一个残酷的现实是reference底层几乎都是指针完成的,所以passing by reference通常意味着传递的是指针,如果你有个小对象char,那么pass by value可能比不pass by reference更高效一些。

条款23:当你必须传回object时,不要尝试传回reference
Albert Einstein说过:尽可能让事情简单,但不要过度简单(make things as simple as possible,but no simpler)。c++有类似格言:尽可能让事情有效率,但是不要过度有效率。
针对22条款,什么传值都是用reference是不好的事情,比如传递一个对象,当函数结束的时候函数内对象就被析构了,此时如果传引用,那么它其实指向了一个并不存在的对象。
函数产生新对象的途径有二:在stack空间或者是在heap空间。定义一个local变量,就是在stack空间产生对象,函数结束产生的所有东西都会出栈,所以你唯一的机会是在heap空间构造对象,然后传回其reference。Heap based objects是靠new 产生出来的,此时是可以传回函数内对象的引用的。但是问题总是不断的出现,你用new产生了一个新的对象,那么谁负责delete呢?此种做法往往造成memory leak,即便程序员知道,试图delete,但是也没办法掌握匿名对象,所以也无法删除。(条款31将告诉你如果要这么写,代码应该长成什么样)。
如果你已注意到,无论在stack还是在heap中都必须容忍函数调用传回值需要调用一次constructor,或许你还记得我们最初的目的是避免这样的一次调用,那么你可能想到一种方法来避免了,就是声明static object,然后传回它的引用,新的问题总是会出现:

// 写此函数的第三个错误方法
inline const rational& operator*(const rational& lhs, const rational& rhs){
static rational result; // 将要作为引用返回的静态对象
lhs和rhs 相乘,结果放进result;
return result;
}

那么请考虑下面一段非常合理的伪代码:

bool operator==(const rational& lhs, const rational& rhs);
rational a, b, c, d;
...
if ((a * b) == (c * d)) {
处理相等的情况;
} else {
处理不相等的情况;
}

现在可以想象这个问题,无论a、b、c、d是什么值,都将为真。要理解这个看了就令人浑身发热的式子我们可以变形一下:if (operator==(operator*(a, b), operator*(c, d)))。注意这个operator==被调用时,已经有两个operator*完成了,这就很明白为什么总为真了,因为他们都指向同一个static rational object,不相等才奇怪。继续写一段书中的幽默,让可能枯燥的你也能一笑:
“ 幸运的话,我以上的说明应该足以说服你:想“在象operator*这样的函数里返回一个引用”实际上是在浪费时间。但我没幼稚到会相信幸运总会光临自己。一些人——你们知道这些人是指谁——此刻会在想,“唔,上面那个方法,如果一个静态变量不够用,也许可以用一个静态数组……”请就此打住!我们难道还没受够吗?”
解释了一通静态数组也不行。。。最后,是的,最佳办法是通过assignment,但成本是什么呢?一般而言差不多等同于一个destructor call加上一个constructor call,但我们不正是为了避免它么?正确的的做法是:

inline const rational operator*(const rational& lhs,const rational& rhs){
return rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

所以当你抉择pass by value 和pass by reference时,必须选择正确的行为,再考虑效率。

条款24:在函数重载和参数缺省化之间,谨慎抉择
会对函数重载和设定参数缺省值产生混淆的原因在于,它们都允许一个函数以多种方式被调用:

void f(); // f被重载
void f(int x);
f(); // 调用f()
f(10); // 调用f(int)
void g(int x = 0); // g 有一个缺省参数值
g(); // 调用g(0)
g(10); // 调用g(10)

那么,什么时候该用哪种方法呢?
答案取决于另外两个问题。第一,确实有那么一个值可以作为缺省吗?第二,要用到多少种算法?一般来说,如果可以选择一个合适的缺省值并且只是用到一种算法,就使用缺省参数(参见条款38)。否则,就使用函数重载。

下面是一个最多可以计算五个int的最大值的函数。这个函数使用了——深呼一口气,看清楚啦——std::numeric_limits::min(),作为缺省参数值。等会儿再进一步介绍这个值,这里先给出函数的代码:

int max(int a,
int b = std::numeric_limits::min(),
int c = std::numeric_limits::min(),
int d = std::numeric_limits::min(),
int e = std::numeric_limits::min()){
int temp = a > b ? a : b;
temp = temp > c ? temp : c;
temp = temp > d ? temp : d;
return temp > e ? temp : e;
}

现在可以放松了。std::numeric_limits::min()是c++标准库用一种特有的新方法所表示的一个在c里已经定义了的东西,即c在 中定义的int_min宏所表示的那个东西——处理你的c++原代码的编译器所产生的int的最小可能值。是的,它的句法背离了c所具有的简洁,但在那些冒号以及其它奇怪的句法背后,是有道理可循的。

假设想写一个函数模板,其参数为固定数字类型,模板产生的函数可以打印用“实例化类型”表示的最小值。这个模板可以这么写:

template
void printminimumvalue(){
cout << 表示为t类型的最小值; }

如果只是借助 来写这个函数会觉得很困难,因为不知道t是什么,所以不知道该打印int_min还是dbl_min,或其它什么类型的值。

为避开这些困难,标准c++库(见条款49)在头文件 中定义了一个类模板numeric_limits,这个类模板本身也定义了一些静态成员函数。每个函数返回的是“实例化这个模板的类型”的信息。也就是说,numeric_limits中的函数返回的信息是关于类型int的,numeric_limits 中的函数返回的信息是关于类型double的。numeric_limits中有一个函数叫min,min返回可表示为“实例化类型”的最小值,所以numeric_limits::min()返回的是代表整数类型的最小值。

有了numeric_limits(和标准库中其它东西一样,numeric_limits存在于名字空间std中;numeric_limits本身在头文件 中),写printminimumvalue就可以象下面这样容易:

template
void printminimumvalue(){
cout << std::numeric_limits::min();
}

采用基于numeric_limits的方法来表示“类型相关常量”看起来开销很大,其实不然。因为原代码的冗长的语句不会反映到生成的目标代码中。实际上,对numeric_limits的调用根本就不产生任何指令。想知道怎么回事,看看下面,这是numeric_limits::min的一个很简单的实现:

#include namespace std {
inline int numeric_limits::min() throw ()
{ return int_min; }
}

因为此函数声明为inline,对它的调用会被函数体代替(见条款33)。它只是个int_min,也就是说,它本身仅仅是个简单的“实现时定义的常量”的#define。所以即使本条款开头的那个max函数看起来好象对每个缺省参数进行了函数调用,其实只不过是用了另一种聪明的方法来表示一个类型相关常量而已(本例中常量值为int_min)。象这样一些高效巧妙的应用在c++标准库里俯拾皆是,这可以参考条款49。

回到max 函数上来:最关键的一点是,不管函数的调用者提供几个参数,max计算时采用的是相同(效率很低)的算法。在函数内部任何地方都不用在意哪些参数是“真”的,哪些是缺省值;而且,所选用的缺省值不可能影响到所采用的算法计算的正确性。这就是使用缺省参数值的方案可行的原因。

对很多函数来说,会找不到合适的缺省值。例如,假设想写一个函数来计算最多可达5个int的平均值。这里就不能用缺省参数,因为函数的结果取决于传入的参数的个数:如果传入3个值,就要将总数除以3;如果传入5个值,就要将总数除以5。另外,假如用户没有提供某个参数时,没有一个“神奇的数字”可以作为缺省值,因为所有可能的int都可以是有效参数。这种情况下就别无选择:必须重载函数:

double avg(int a);
double avg(int a, int b);
double avg(int a, int b, int c);
double avg(int a, int b, int c, int d);
double avg(int a, int b, int c, int d, int e);

另一种必须使用重载函数的情况是:想完成一项特殊的任务,但算法取决于给定的输入值。这种情况对于构造函数很常见:“缺省”构造函数是凭空(没有输入)构造一个对象,而拷贝构造函数是根据一个已存在的对象构造一个对象:

// 一个表示自然数的类
class natural {
public:
natural(int initvalue);
natural(const natural& rhs);
private:
unsigned int value;
void init(int initvalue);
void error(const string& msg);
};
inline void natural::init(int initvalue) { value = initvalue; }
natural::natural(int initvalue){
if (initvalue > 0) init(initvalue);
else error("illegal initial value");
}
inline natural::natural(const natural& x)
{ init(x.value); }
输入为int的构造函数必须执行错误检查,而拷贝构造函数不需要,所以需要两个不同的函数来实现,这就是重载。还请注意,两个函数都必须对新对象赋一个初值。这会导致在两个构造函数里出现重复代码,所以要写一个“包含有两个构造函数公共代码”的私有成员函数init来解决这个问题。这个方法——在重载函数中调用一个“为重载函数完成某些功能”的公共的底层函数——很值得牢记,因为它经常有用(见条款12)。
ps:上述例子有更加简洁有效的方法,不定参数函数,请看另一篇文章

条款25:避免对指针型别和数值型别进行重载
大家可以尝试回答:0是什么?
或者看下边的程序会发生什么?
void f(int x);
void f(string *ps);
f(0); //会调用哪一个?答案是int这个。
所以为什么要此条款,解决。书中其实说了不少,其中有一个很惊奇的程序:

#include
#include
using namespace std;
const class{
public:
template
operator T*()const{return 0;}//可转换为任意型别的null non-member pointer
template
operator T C::*() const{return 0;}//可转换任意型别的null member pointer
private:
void operator&() const;
}null;
void f(int x){ cout<<"int";} void f(string *st){ cout<<"string";} int main(){ f(null);//这里就调用了string的那个函数。 }

条款26:防卫潜伏的ambiguity(模棱两可)状态
先看例子:

class B; // 对类B提前声明
class A {
public:
A(const B&); // 可以从B构造而来的类A
};
class B {
public:
operator A() const; // 可以从A转换而来的类B
};

这些类的声明没一点错——他们可以在相同的程序中共存而没一点问题。但是,看看下面,当把这两个类结合起来使用,在一个输入参数为A的函数里实际传进了一个B的对象,这时将会发生什么呢?
void f(const A&);
B b;
f(b); // 错误!——二义
一看到对f的调用,编译器就知道它必须产生一个类型A的对象,即使它手上拿着的是一个类型B的对象。有两种都很好的方法来实现(见条款M5)。一种方法是调用类A的构造函数,它以b为参数构造一个新的A的对象。另一种方法是调用类B里自定义的转换运算符,它将b转换成一个A的对象。因为这两个途径都一样可行,编译器拒绝从他们中选择一个。

当然,在没碰上二义的情况下,程序可以使用。这正是潜在的二义所具有的潜伏的危害性。它可以长时期地潜伏在程序里,不被发觉也不活动;一旦某一天某位不知情的程序员真的做了什么具有二义性的操作,混乱就会爆发。这导致有这样一种令人担心的可能:你发布了一个函数库,它可以在二义的情况下被调用,而你却不知道自己正在这么做。

另一种类似的二义的形式源于C++语言的标准转换——甚至没有涉及到类:

void f(int);
void f(char);
double d = 6.02;
f(d); // 错误!——二义

d是该转换成int还是char呢?两种转换都可行,所以编译器干脆不去做结论。幸运的是,可以通过显式类型转换来解决这个问题:

f(static_cast(d)); // 正确, 调用f(int)
f(static_cast(d)); // 正确, 调用f(char)

多继承(见条款43)充满了潜在二义性的可能。最常发生的一种情况是当一个派生类从多个基类继承了相同的成员名时:

class Base1 {
public:
int doIt();
};
class Base2 {
public:
void doIt();
};
class Derived: public Base1, // Derived没有声明
public Base2 { // 一个叫做doIt的函数
...
};
Derived d;
d.doIt(); // 错误!——二义

当类Derived继承两个具有相同名字的函数时,C++没有认为它有错,此时二义只是潜在的。然而,对doIt的调用迫使编译器面对这个现实,除非显式地通过指明函数所需要的基类来消除二义,函数调用就会出错:

d.Base1::doIt(); // 正确, 调用Base1::doIt
d.Base2::doIt(); // 正确, 调用Base2::doIt

这不会令很多人感到麻烦,但当看到上面的代码没有用到访问权限时,一些本来很安分的人会动起心眼想做些不安分的事:

class Base1 { ... }; // 同上
class Base2 {
private:
void doIt(); // 此函数现在为private
};
class Derived: public Base1, public Base2
{ ... }; // 同上
Derived d;
int i = d.doIt(); // 错误! — 还是二义!

对doIt的调用还是具有二义性,即使只有Base1中的函数可以被访问。另外,只有Base1::doIt返回的值可以用于初始化一个int这一事实也与之无关——调用还是具有二义性。如果想成功地调用,就必须指明想要的是哪个类的doIt。

C++中有一些最初看起来会觉得很不直观的规定,现在就是这种情况。具体来说,为什么消除“对类成员的引用所产生的二义”时不考虑访问权限呢?有一个非常好的理由,它可以归结为:改变一个类成员的访问权限不应该改变程序的含义。

比如前面那个例子,假设它考虑了访问权限。于是表达式d.doIt()决定调用Base1::doIt,因为Base2的版本不能访问。现在假设Base1的Doit版本由public改为protected,Base2的版本则由private改为public。

转瞬之间,同样的表达式d.doIt()将导致另一个完全不同的函数调用,即使调用代码和被调用函数本身都没有被修改!这很不直观,编译器甚至无法产生一个警告。可见,不是象你当初所想的那样,对多继承的成员的引用要显式地消除二义性是有道理的。

既然写程序和函数库时有这么多不同的情况会产生潜在的二义性,那么,一个好的软件开发者该怎么做呢?最根本的是,一定要时时小心它。想找出所有潜在的二义性的根源几乎是不可能的,特别是当程序员将不同的独立开发的库结合起来使用时(见条款28),但在了解了导致经常产生潜在二义性的那些情况后,你就可以在软件设计和开发中将它出现的可能性降到最低。

条款27:如果不想使用编译器暗自产生的member functions,就应该明白拒绝它
比如直接声明成private member。如果你还不放心,因为考虑到可能出现的不靠谱friend,那么你就直接在private域内声明,但是不要定义它,一切都OK了。

条款28:尝试切割global namespace(全局命名空间)
在c++大量类库面前,如果你足够幸运,你可以写出大量自己定义的名字而不遇到重复,但倒霉的家伙总是存在。一个比较好的方法就是使用c++ namespace,这样就不需要为了避免可能的重复写那么常常的前缀,直接写成这样:

namespace sdm{
const double BOOK_VERSION =2.0;
class Handle{...};
Handle& getHandle();
}

clients可以有三种方法获得namespace内的符号:一是将namespace内的所有符号汇入(importing)一个scope内,二是将个别符号汇入一个scope内,三是每次使用符号都明确的加上资格修饰。如下实例:

void f1(){
using namespace sdm; // 使得sdm中的所有符号不用加修饰符就可以使用
cout << book_version; // 解释为sdm::book_version ... handle h = gethandle(); // handle解释为sdm::handle gethandle解释为sdm::gethandle ... } void f2(){ using sdm::book_version; // 使得仅book_version不用加修饰符就可以使用 cout << book_version; // 解释为sdm::book_version ... handle h = gethandle(); // 错误! handle和gethandle都没有引入到本空间 ... } void f3(){ cout << sdm::book_version; // 使得book_version在本语句有效 ... double d = book_version; // 错误! book_version不在本空间 handle h = gethandle(); // 错误! handle和gethandle都没有引入到本空间 ... }

另外有某些namespaces本身没有名字,这种匿名namespaces系用来限制其中元素的可见度。namespaces也能完美的解决模棱两可问题,你也可以混合使用namespaces,如果多个namesapces内都有同一个名字,那么你仅仅需要明确指出是哪个命名空间的就好了。
如果没有编译器的支持仍然可以用struct来仿真namespaces:

struct sdm{
static const double BOOK_VERSION;
class Handle{...};
static Handle& getHandle();
};
const double sdm::BOOK_VERSION=2.0;

有没有类似enum来类似的创建常量?所以记得变通,事情总是有办法解决的。

如果项目比较小,不会有命名冲突的可能,使用namespace感觉有点麻烦,有一个方法可以拥有多个scope,又很方便的忽略之。那就是针对你的型别名称提供typedefs,去除“explicit scoping”的需要。如:typedef sdm::Handle Handle;

struct是namespace的很好的近似,但实际上还是相差很远。它在很多方面很欠缺,其中很明显的一点是对运算符的处理。如果运算符被定义为结构的静态成员,它就只能通过函数调用来使用,而不能象常规的运算符所设计的那样,可以通过自然的中缀语法来使用:

// 定义一个模拟名字空间的结构,结构内部包含widgets的类型
// 和函数。widgets对象支持operator+进行加法运算
struct widgets {
class widget { ... };
// 参见条款21:为什么返回const
static const widget operator+(const widget& lhs, const widget& rhs);
...
};
// 为上面所述的widge和operator+ 建立全局(无修饰符的)名称
typedef widgets::widget widget;
const widget (* const operator+)(const widget&,const widget&); //错误! operator+不能是指针名
widget w1, w2, sum;
sum = w1 + w2; // 错误! 本空间没有声明参数为widgets 的operator+
sum = widgets::operator+(w1, w2); // 合法, 但不是"自然"的语法

正因为这些限制,所以一旦编译器支持,就要尽早使用真正的名字空间。

我猜你可能也喜欢:

No Comments - Leave a comment

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>


Welcome , today is 星期二, 2017 年 10 月 24 日