template <class T> class visibility { public: void say(double d){}; private: void say(int i){}; void say(T t){}; }; int _tmain(int argc, _TCHAR* argv[]) { visibility<char*> v; v.say(123); // error C2248: ‘visibility<T>::say‘ : cannot access private member declared in class ‘visibility<T>‘ // 重载决议后: void say(int i){}; v.say("123"); // error C2248: ‘visibility<T>::say‘ : cannot access private member declared in class ‘visibility<T>‘ // 重载决议后: void say(T t){}; return 0; }
0 访问限制:
1 private: 它的名字只能被所声明的类的成员和友元使用。 2 protected: 它的名字可以被所声明的类的成员和友元使用,也可以被派生类的成员和友元使用 3 public: 它的名字可以无限制的用在任何地方。
1 发生在调用之前
cpp文件中调用的一个函数(或成员函数),编译器主要做了下面四件事情:
1).Name lookup(名字查找)
确定一系列的候选者。(注意所谓 ‘名字查找‘ 中的 ‘名字‘ 仅仅是函数名, 而不是函数签名, 即不包括返回值, 参数)
(1) 范围查找, 范围一旦确定下来, 就停止范围查找.
a) 如果有限定名 (X::func), 则在X::中查找; 找不到就 error;
b) 如果是成员函数(object->func), 则在类中查找, 找不到,再去基类中查找; 找不到就 error;
c) 如果形如 `func()` 的函数, 查找规则如下:
<c-1> 外层函数(调用者)所在类中查找; 找不到就到 <c-2>;
<c-2> 基类中查找, 找不到再去外层基类中查找(baseclass -> 外层的 baseclass -> ...); 一旦找到就停止查找; 找不到就到 <c-3>;
eg: void A::say() { func(); } 去say 所在的类A中找, 找不到再去A的基类中找。
<c-3> 在当前的namespace 中查找; 找不到再去外层 namespace 中查找(当前的 namespace-> 外层的 namespace -> 更外层的 namespace....) ; 一旦找到就停止查找; 找不到就 error;
注意: 如果函数参数是 class/struct, 那么由于koening 查询, 参数类型所在的 namespace 也会被列为查找范围。所以参数类型所在的 namespace 中的名字也会被列入到候选范围。
eg: 这里在 namespace Lv2 中找到了 ‘func‘, 通过 koening 在 koeningNs 中也找到了 ‘func‘, 所以 (III) 查找到的候选者有: { (I) koeningNs::func , (II) Lv3::Lv2::func }
namespace koeningNs { class KCls {}; void func(KCls){} // (I) } namespace Lv3{ namespace Lv2 { void func(koeningNs::KCls){ } // (II) namespace Lv1 { class myCls { public : void say() { func(koeningNs::KCls()); // (III) // error C2668: ‘Lv3::Lv2::func‘ : ambiguous call to overloaded function } }; } } } void test_nn() { Lv3::Lv2::Lv1::myCls obj; obj.say(); };
<c-4> 最后确定候选者们 : { 类中的候选者们 } or { 基类中的候选者们 } or { namespace 中的候选者们}
注意 : 一旦编译器确定了一个候选者集合, 就停止查找。比如 : 找到了 { 类中的候选者们 }, 那么就不会再关注 { 基类中的候选者们 } 更会不关注 { namespace 中的候选者们}。
(2) 在找到的范围中, 确定一系列候选者
比如: 在class 中找到了say, 那么可以确定候选者:
void say(double d);
void say(int i);
2.Overload resolution(重载决议)
编译器开始执行重载决议, 根据参数最匹配原则,从候选者中选出最匹配的函数。如不唯一,就存在二义性。
eg: int say(int i) 被选中.
优先级:
1) 非模板函数
2) 如果编译器没有发现合适的非模板函数, 那么主函数模板被纳入考虑.
函数模板特化并不参与重载决议.只有在某个主模板被重载的决议前提下,其特化版本才有可能被使用,而且编译器在选择主模板的时候并不关心他是否有某个特化版本.
因为函数特化模板不参与重载决议, 所以, 如果想运用某个函数的特化, 最好的方法是重载该函数。
3.Accessibility checking(可访问性检测).
编译器执行可访问性检测来决定被选中的函数是否能被调用。
重载决议发生在可访问性检查之前. 因此,如果私有函数不幸参与了重载,并且被选中,最终也会出现无法访问的编译提示.
这常常隐含二义性,这样的设计本身也不合理.换句话说,私有参数,在名字查找和重载时并非是私有的.
注意, 针对模板函数, 这个阶段只检测主模板函数, 而不会管模板函数的特化形式。也就是说如果无法访问主模板函数, 即使模板函数的特化版本是public, 也没用。
4. 如果选中的是模板函数, 那么还要进行模版特化决议
class CTemp { public: template <typename T> void sayHello() { cout << " sayHello : 主模板" << endl; } private: template <> void sayHello<int>() { cout << "sayHello : 特化模板" << endl; } private: template <typename T> void sayBonjour() { cout << " sayBonjour : 主模板" << endl; } public: template <> void sayBonjour<int>() { cout << " sayBonjour : 特化板" << endl; } }; void test_nn() { CTemp ct; ct.sayHello<int>(); // 主模板 public, 特化模板 private // 可以调用特化的 private. ct.sayBonjour<int>(); // error C2248: ‘CTemp::sayBonjour‘ : cannot access private member declared in class ‘CTemp‘ // 主模板 private, 特化模板 public // 主模板在 ‘可访问性检查‘ 的时候就被认为 ‘不可访问‘, 即使特化模板 public 也不行。 };