c++ 中的函数查找

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 也不行。
};

 

原文地址:https://www.cnblogs.com/happylong/p/4320810.html