在C++中,若是編譯器遇到一個名稱,它會尋找這個名稱表明什麼。好比x*y,若是x和y是變量的名稱,那麼就是乘法。若是x是一個類型的名稱,那麼就聲明瞭一個指針。ios
C++是一個context-sensitive
的語言 : 必須知道上下文才能知道表達式的意義。那麼這個和模板的關係是什麼呢?構造一個模板必須知道幾個上下文:c++
引入兩個重要的概念:express
::
、->
或者.
。this->count
就是一個qualified name,但count不是,由於它的做用域沒有被顯示的指明,即便它和this->count
是等價的。std::vector<T>::iterator
. 但假如T是一個已知類型的別名(using T = int),那麼就不是dependent name.名稱查找有不少細節,這裏咱們只關注幾個主要的點。app
對於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出如今C++98/C++03中,也被叫作Koenig lookup,應用在非qualified name上(下文簡稱unqualified name)。在函數調用表達式中(f(a1, a2, a3, ... ),包含隱式的調用重載operator,例如 << ),ADL應用一系列的規則來查找unqualified function names
。
ADL會將函數表達式中實參的associated namespaces
和associated classes
加入到查找範圍,這也就是爲何叫Argument-Dependent Lookup. 例如:某一類型是指向class X的指針,那麼它的associated namespaces
和associated classes
會包含X和X所屬的任何class和namespace.
對於給定的類型,associated classes
和associated 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有可能會致使語義問題,這也是爲何有的時候須要在函數前面加::
,或者通常推薦使用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; }
(完)
朋友們能夠關注下個人公衆號,得到最及時的更新: