c++11-17 模板核心知識(十三)—— 名稱查找與ADL

在C++中,若是編譯器遇到一個名稱,它會尋找這個名稱表明什麼。好比x*y,若是x和y是變量的名稱,那麼就是乘法。若是x是一個類型的名稱,那麼就聲明瞭一個指針。ios

C++是一個context-sensitive的語言 : 必須知道上下文才能知道表達式的意義。那麼這個和模板的關係是什麼呢?構造一個模板必須知道幾個上下文:c++

  • 模板出現的上下文
  • 模板被實例化的上下文
  • 實例化模板參數的上下文

名稱分類

引入兩個重要的概念:express

  • qualified name : 一個名稱所屬的做用域被顯式的指明,例如::->或者.this->count就是一個qualified name,但count不是,由於它的做用域沒有被顯示的指明,即便它和this->count是等價的。
  • dependent name:依賴於模板參數。例如:std::vector<T>::iterator. 但假如T是一個已知類型的別名(using T = int),那麼就不是dependent name.

image

名稱查找

名稱查找有不少細節,這裏咱們只關注幾個主要的點。app

ordinary lookup

對於qualified name來講,會有顯示指明的做用域。若是做用域是一個類,那麼基類也會被考慮在內,可是類的外圍做用域不會被考慮:less

int x;

class B {
public:
  int i;
};

class D : public B {};

void f(D *pd) {
  pd->i = 3;    // finds B::i
  D::x = 2;      // ERROR: does not find ::x in the enclosing scope
}

這點很符合直覺。函數

相反,對於非qualified name來講,會在外圍做用域逐層查找(假如在類成員函數中,會先找本類和基類的做用域)。這叫作ordinary lookup :this

extern int count;             // #1

int lookup_example(int count) // #2
{
  if (count < 0) {
    int count = 1;           // #3
    lookup_example(count);   // unqualified count refers to #3
  }
  return count + ::count;      // the first (unqualified) count refers to #2 ;
}                              // the second (qualified) count refers to #1

這個例子也很符合直覺。spa

可是下面這個例子就沒那麼正常:.net

template<typename T>
T max (T a, T b) {
    return b < a ? a : b;
}

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時,ordinary lookup不會找到BigNumber的operator <。若是沒有一些特殊規則,那麼在C++ namespace場景中,會極大的限制模板的適應性。ADL就是這個特殊規則,用來解決此類的問題。指針

ADL (Argument-Dependent Lookup)

ADL出如今C++98/C++03中,也被叫作Koenig lookup,應用在非qualified name上(下文簡稱unqualified name)。函數調用表達式中(f(a1, a2, a3, ... ),包含隱式的調用重載operator,例如 << ),ADL應用一系列的規則來查找unqualified function names

ADL會將函數表達式中實參的associated namespacesassociated classes加入到查找範圍,這也就是爲何叫Argument-Dependent Lookup. 例如:某一類型是指向class X的指針,那麼它的associated namespacesassociated classes會包含X和X所屬的任何class和namespace.

對於給定的類型,associated classesassociated namespaces按照必定的規則來定義,你們能夠看下官網Argument-dependent lookup,實在有點多,不寫在這裏了。理解爲何須要ADL、何時應用到ADL時,按照對應的場景再去查就行~

額外須要注意的一點是,ADL會忽略using :

#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\n"; }
}    // namespace N

void f(int) { std::cout << "::f(int) called\n"; }

int main() {
  ::f(N::e1);    // qualified function name: no ADL
  f(N::e1);     // ordinary lookup finds ::f() and ADL finds N::f(), the latter is preferred
}

namespace N中的using namespace X會被ADL忽略,因此在main函數中,X::f()不會被考慮。

官網的例子

看下官網的例子幫助理解:

#include <iostream>
int main() {
    std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
                           // examines std namespace because the left argument is in
                           // std and finds std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // same, using function call notation
 
    // however,
    std::cout << endl; // Error: 'endl' is not declared in this namespace.
                       // This is not a function call to endl(), so ADL does not apply
 
    endl(std::cout); // OK: this is a function call: ADL examines std namespace
                     // because the argument of endl is in std, and finds std::endl
 
    (endl)(std::cout); // Error: 'endl' is not declared in this namespace.
                       // The sub-expression (endl) is not a function call expression
}

注意最後一點(endl)(std::cout);,若是函數的名字被括號包起來了,那也不會應用ADL。

再來一個:

namespace A {
      struct X;
      struct Y;
      void f(int);
      void g(X);
}
 
namespace B {
    void f(int i) {
        f(i);      // calls B::f (endless recursion)
    }
    void g(A::X x) {
        g(x);   // Error: ambiguous between B::g (ordinary lookup)
                //        and A::g (argument-dependent lookup)
    }
    void h(A::Y y) {
        h(y);   // calls B::h (endless recursion): ADL examines the A namespace
                // but finds no A::h, so only B::h from ordinary lookup is used
    }
}

這個比較好理解,不解釋了。

ADL的缺點

依賴ADL有可能會致使語義問題,這也是爲何有的時候須要在函數前面加::,或者通常推薦使用xxx::func,而不是using namespace xxx 。由於前者是qualified name,沒有ADL的過程。

引用現代C++之ADL中的例子,只看swap就行,類的其餘函數能夠略過:

#include <iostream>
 
namespace A {
    template<typename T>
    class smart_ptr {
    public:
        smart_ptr() noexcept : ptr_(nullptr) {
 
        }
 
        smart_ptr(const T &ptr) noexcept : ptr_(new T(ptr)) {
 
        }
 
        smart_ptr(smart_ptr &rhs) noexcept {
            ptr_ = rhs.release();       // 釋放全部權,此時rhs的ptr_指針爲nullptr
        }
 
        smart_ptr &operator=(smart_ptr rhs) noexcept {
            swap(rhs);
            return *this;
        }
 
        void swap(smart_ptr &rhs) noexcept { // noexcept == throw() 保證不拋出異常
            using std::swap;
            swap(ptr_, rhs.ptr_);
        }
 
        T *release() noexcept {
            T *ptr = ptr_;
            ptr_ = nullptr;
            return ptr;
        }
 
        T *get() const noexcept {
            return ptr_;
        }
 
    private:
        T *ptr_;
    };
 
// 提供一個非成員swap函數for ADL(Argument Dependent Lookup)
    template<typename T>
    void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
        lhs.swap(rhs);
    }
}
 
// 開啓這個註釋,會引起ADL衝突
//namespace std {
//    // 提供一個非成員swap函數for ADL(Argument Dependent Lookup)
//    template<typename T>
//    void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
//        lhs.swap(rhs);
//    }
//
//}
 
int main() {
 
    using std::swap;
    A::smart_ptr<std::string> s1("hello"), s2("world");
    // 交換前
    std::cout << *s1.get() << " " << *s2.get() << std::endl;
    swap(s1, s2);      // 這裏swap 可以經過Koenig搜索或者說ADL根據s1與s2的命名空間來查找swap函數
    // 交換後
    std::cout << *s1.get() << " " << *s2.get() << std::endl;
}

(完)

朋友們能夠關注下個人公衆號,得到最及時的更新:

image

相關文章
相關標籤/搜索