类影响函数重载的候选函数集

当函数的实参:
1。类对象
2。指向对象的指针
3。对象的引用
4。指向成员的指针
时,重载函数的候选集大大扩大,类的友元函数加入候选集
现在的问题是:
类定义所在的名字空间的同名函数加入候选集呢(N1),还是类声明所在的名字空间的同名函数加入候选集(N2+N3,定义可以当作声明,声明可以多个)

代码中的只能看见全局的add,待验证

namespace N3
{
    namespace N2
    {
        namespace N1
        {
            class A //定义
            {
                friend void add(){cout << "friend" << endl;}
            };
            void add(A a){cout << "N1" << endl;}//友元函数相当于在这里定义,所以如果参数相同会报重复定                                                                               //义错,因为友元函数不属于类的内容,和类定义并列,即使                                                                               //它的函数体写在类体中
        }
        using N1::A; //声明 class N1::A会提示未定义类型A
        void add(A a, int){cout << "N2" << endl;}
    }
}

void add(N3::N2::N1::A , int ,int )
{
    cout << "global" << endl;
}

int main()
{
    N3::N2::N1::A tmp;
    add(tmp);//error C2660: 'add' : function does not take 1 parameters
    add(tmp, 1);//error C2660: 'add' : function does not take 2 parameters
    add(tmp, 1, 1);//ok
    return 0;
}

应该是把类定义所在的名字空间加入重载函数的搜索域中,也就是N1名字空间,声明承诺了存在定义,存在实体,从而告知本域中的成员可以使用这个类,声明保证它存在于程序某处,如果声明加入搜索域,编译器处理的范围那太大,效率低,就像只有当左操作数是类对象的时候,才会查找类成员的重载操作符,“abc”==text不会利用Text(const char*)先构造个临时对象一样,因为支持这种用法,编译器需扫描的整个所在域中的类定义,看是否存在这个构造函数,这样效率低

#include <iostream>
using namespace std;
namespace N3
{
    namespace N2
    {
        namespace N1
        {
            class A //定义
            {
                friend void add(){cout << "friend" << endl;}
            };
            void add(A a){cout << "N1" << endl;}
        }
        class N1::A; //声明
        void add(N1::A a, int){cout << "N2" << endl;}
    }
}

void add(N3::N2::N1::A , int ,int )
{
    cout << "global" << endl;
}

int main()
{
    N3::N2::N1::A tmp;
    add(tmp);
    add(tmp, 1);
    add(tmp, 1, 1);
    return 0;
}

在dev0-C++中验证,类定义所在的名字空间导入重载函数的搜索域。而并非声明。
class N1::A;
属于声明,后面使用时仍需N1::域修饰符
using N1::A;
属于声明,由于其的特殊的语法,使得其后使用的时候直接的A

晨星解释:

Koenig查找规则就是你从C++ Primer上看到的那个跟参数有关的查找规则。

对于你原来的问题,编译器在使用参数相关查找时,所有那些在声明那些类型namespace中找到的函数统统是候选,这一点是没什么疑问的。
所以,可以认为,“参数相关查找”是按声明来看的,而定义,它本身同时就是一种声明。

至于你说的“声明的话可以多次声明,那样岂不是引入一个巨大的搜索范围”,这个你说的虽然没错,但却是过虑了,钻了牛角。
没错,只要能看到声明的都算,但你别忘了这句话里还有个很大的前提:“能看到的”。
最简单的例子:头文件a.h中定义了名字空间B,并在名字空间B中声明了名字C,那么,使用所有用到C类型参数的函数,理论上讲都要考虑名字空间B中的函数。但前提当然是你得#include a.h。——这难道不是显然的吗?:)
所以,如果我们不想让这里的B污染代码,尽量别包含a.h就是了。
再说,在两个名字空间中声明的同样的名字也不见得就指代同一事物。这一点别忘了,而编译器当然只考虑它所认为的那个被指代的。

你的原来的例子确实是VC6搞错了。

最后,偶也写了个例子,这个例子中,所有的类类型统统只有声明,没有一个定义,事实上我根本也没在任何其它地方定义它们,但至少在这个例子中,程序可以顺利编译通过并成功运行,一点问题都没有。可见你原来的问题答案只有一个:一切看声明(而定义此时所起的作用跟声明是一样的)。

#include <iostream>
using namespace std;

namespace N1 {
class A;
void f(A* a) { cout << "N1::f(A*)"  << endl;}
}

namespace N2 {
using N1::A;
class B;
void f(A* a, B* b) { cout << "N2::f(A*, B*)" << endl; };
}

void f(void* p,void* q) { cout << "::f(void*, void*)" << endl; }
class C;

int main()
{
N2::A* a = 0;
N2::B* b = 0;
C* c = 0;

f(a);
f(a, b);
f(a, c);
return 0;
}

最后,还有一点是要区分“声明”和“using声明”(using declaration),using声明跟一般的类声明,函数声明和变量声明不一样。它只是提供了一种更方便的手段,让我们可以在某个名字空间中使用另外一个空间中的名字,而不必加namespace修饰。但它并不影响Koenig查找,影响Koenig查找结果的是一般的实体声明,不包含“using声明”。比如:

#include <iostream>
using namespace std;

namespace N1 {
class A;
}

namespace N2 {
using N1::A;
void f(A* a) { cout << "N3::f(A*)" << endl; }
}

void f(void* p) { cout << "::f(void*)" << endl; }

int main() {
N2::A* a = 0;
f(a);
return 0;
}
最后的结果是“一般”匹配“::f”,而非“更精确的匹配”:“N1::f”。
倘若你把全局的“::f”注释掉,编译会失败。

那么,为什么第一个例子中却能成功找到:“N2::f(A*,B*)”呢?那是因为编译器凭借的是N2::B的参数相关查找。——仅凭N2::B自已,编译器已足以找到“N2::f(A*, B*)”,至于N2::A(当然也是N1::A),虽然它无法在编译器查找名字时帮上一忙,但编译器已经不在乎了。:)

高手指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值