C++模板编程(14)---名称查询(Looking Up Names)

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;

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值