0.简介
C++语言的名称搜寻机制设计众多细节,我们只集中在数个主要概念上,为了保证:
1)通常情况下,这些查询符合人们的直觉;
2)特别复杂的情况可以在C++standard中找到答案。
受饰名称的查询范围是在其“修饰构件所处的作用域”(the scope implied by the qualifying construct)内。如果该作用域是个class,则其base class也将被查询。然而编译器查询受饰名称时,并不考虑圈封作用域enclosing scopes。下面的例子阐释了这个基本法则:
int x;
class B{
public:
int i;
};
class D: public B{};
void f(D* pd)
{
pd->i =3; //编译器找到B::i(pd->i 是个 qualified name)
D::x =2; //Error, 在D作用域中(包括B)也找不到x
}
与之形成对比的是,编译器通常会依次在更外层的圈封作用域(more enclosing scopes)中查询非受饰名称unqualified names,尽管在成员函数定义内编译器会先查询class作用域和base class作用域,然后才是其他圈封作用域。这便是所谓的ordinary lookup(常规查询)。
下面例子展示ordinary lookup的基本概念:
extern int count; //(1)
int lookup_example(int count) //(2)
{
if(count < 0) {
int count = 1; //(3)
lookup_example(count); // 非受饰的count的代表是(3)
}
return count += ::count; //第一个count(非受饰)代表(2)
//第二个::count(受饰)代表(1)
}
在常规查询之外,还有一种用法来查询非受饰名称unqualified names.这种机制有时也称为依赖于实参的查询,argument-dependent lookup,ADL。深入ADL之前,先介绍要给引发此机制的例子:
template <typename T>
inline T const & max (T const&, a, T const& b)
{
return a<b ? b:a;
}
现在假设我们需要把这个template应用到定义于另一个namespace内的类型身上:
namespace BigMath{
class BigNumber{
...
};
bool operator< (BigNumber const&, BigNumber const&);
...
}
using BigMath::BigNumber;
void g(BigNumber const& a, BigNumber const& b)
{
...
BigNumber x= max(a,b);
...
}
这里的问题在于:max() template 对BigMath namespace一无所知,而ordinary lookup常规查询机制无法找到一个operator < 可施行于BigNumber类型。如果没有某种特殊规则,这个问题就大大缩减了templates在C++ namespaces 情形下的应用性。一个解决办法就是ADL。
1.依赖于实参的查询Argument-Dependent Lookup, ADL
ADL只适用于这样的非受饰名称unqualified names:在函数调用动作中用到的一个非成员函数名称。如果ordinary lookup可找到一个成员函数名称或一个类型名称,编译器就不启动ADL。如果被调用函数的名称被写进一对小括号内,ADL也不起作用。
#include <iostream>
namespace X {
template<typename T> void f(T);
}
namespace N {
using namespace X;
enum E {e1};
void f(E) {
std::cout << "N::f(N::E) called" << std::endl;
}
}
void f(int)
{
std::cout << "::f(int ) called" << std::endl;
}
int main()
{
::f(N::ee1); //受饰函数名称,不使用ADL
f(N::e1); // ordinary lookup,找到::f,而ADL找到N::f(),编译器会优先考虑后者
}
这个例子中, ADL机制中的namespace N中的using指令被忽略。因此main()中的f()不会被编译器认为是对X::f()调用。
2.友元名称植入Friend Name Inject
friend function声明语句可以是该函数的第一份声明语句。这种情况下,该函数会被视为声明于封住class X的最内层namespace(有可能是global namespace)作用域中。这个声明在接受植入的作用域是否可见,是经常争论的问题。这个问题很大程度上,是templates带来的。考虑下面例子:
template <typename T>
class C {
...
friend void f();
friend void f(C<T> const&);
...
};
void g(C<int>* p)
{
f(); //此处可见f()?
f(*p); //此处可见f(C<int> const&)?
}
问题在于,如果friend 声明语句在其圈封之命名空间enclosing namespace中可见,那么当我们实例化一个class template时会造成常规函数ordinary function的声明也可见。C++标准规定friend声明语句不得造成其常规函数名称在圈封作用域enclosing namespace中可见。
依靠只在friend声明语句中声明或定义函数来实现。标准规定当函数的friend class符合ADL规则的相关联classes中的一个时,该friend function 可见。
上面的例子中,由于f()调用中没有引数,因此它没有相关联的classes或namespaces,所以上例中的f()是非法调用。然而f(*p)确实与class C<int>相关联(因为后者是*p的类型),也与global namespace相关联。
3. 植入类名称Injected Class Names
Class 名称会被植入class自身作用域中,因此你可以在该作用域透过未受饰名称unqualified name形式来使用该名称。不能透过受饰名称qualified name来存取它,因为受饰名称在语法上被用来表示该class的构造函数。举个例子:
#include <iostream>
int C;
class C {
private:
int i[2];
public:
static int f() {
return sizeof(C);
}
};
int f()
{
return sizeof(C);
}
int main()
{
std::cout << "C:f()= " << C::f() << ","
<< " ::f() = " << ::f() << std::endl;
}
成员函数C::f()传回类型C的大小,::f()传回变量C的大小,两者都表示一个int object的大小。
class templates也会內植class 名称,但和常规的内植class名称比起来有点特别:
它们的后面可以跟着template argument。如果后面不跟着template argument,代表的是以参数为自变量(对class template偏特化来说则是作为特化用自变量)的class。
template <template <typename > class TT>
class X {
};
template <typename T> class C {
C* a; //OK, 与 C<T>* a;相同
C<void> b; //OK
X<C> c; //Error:
X<::C> d; //Error <: 是 [ 的另一种写法
X< ::C> e; //OK;
};