zoukankan      html  css  js  c++  java
  • c++11-17 模板核心知识(十三)—— 名称查找与ADL

    在C++中,如果编译器遇到一个名称,它会寻找这个名称代表什么。比如x*y,如果x和y是变量的名称,那么就是乘法。如果x是一个类型的名称,那么就声明了一个指针。

    C++是一个context-sensitive的语言 : 必须知道上下文才能知道表达式的意义。那么这个和模板的关系是什么呢?构造一个模板必须知道几个上下文:

    • 模板出现的上下文
    • 模板被实例化的上下文
    • 实例化模板参数的上下文

    名称分类

    引入两个重要的概念:

    • qualified name : 一个名称所属的作用域被显式的指明,例如::->或者.this->count就是一个qualified name,但count不是,因为它的作用域没有被显示的指明,即使它和this->count是等价的。
    • dependent name:依赖于模板参数。例如:std::vector<T>::iterator. 但假如T是一个已知类型的别名(using T = int),那么就不是dependent name.

    image

    名称查找

    名称查找有很多细节,这里我们只关注几个主要的点。

    ordinary lookup

    对于qualified name来说,会有显示指明的作用域。如果作用域是一个类,那么基类也会被考虑在内,但是类的外围作用域不会被考虑:

    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 :

    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
    

    这个例子也很符合直觉。

    但是下面这个例子就没那么正常:

    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
    "; }
    }    // namespace N
    
    void f(int) { std::cout << "::f(int) called
    "; }
    
    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
    "; // 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
    "); // 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

  • 相关阅读:
    P4718 [模板]Pollard-Rho算法
    python爬虫模板
    Codeforces1248F. Catowice City
    P3980 [NOI2008]志愿者招募 (费用流)
    P2805 [NOI2009]植物大战僵尸 (拓扑排序 + 最小割)
    P3157 [CQOI2011]动态逆序对
    P2634 [国家集训队]聪聪可可 (点分治)
    HDU6703 array (线段树)
    Codeforces750E. New Year and Old Subsequence (线段树维护DP)
    Codeforces301D. Yaroslav and Divisors
  • 原文地址:https://www.cnblogs.com/zhangyachen/p/14091740.html
Copyright © 2011-2022 走看看