当前位置: 代码迷 >> 综合 >> Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (3)
  详细解决方案

Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (3)

热度:81   发布时间:2023-12-16 19:41:27.0
Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (1)
Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (2)

Roger( roger2yi@gmail.com.cn)

在前一篇文章的最后讲到Koenig查找会带来些副作用,其实之所以写这系列文章,起源是来自于我在看Boost库noncopyable类源码时的一些疑问:
namespace  boost  {
 

// Private copy constructor and copy assignment ensure classes derived from
// class noncopyable cannot be copied.
 
// Contributed by Dave Abrahams
 

namespace noncopyable_ // protection from unintended ADL
{
 
class noncopyable
 
{
   
protected:
      noncopyable() 
{}
      
~noncopyable() {}

   
private// emphasize the following members are private
      noncopyable( const noncopyable& );
      
const noncopyable& operator=const noncopyable& );
 }
;
}

 
typedef noncopyable_::noncopyable noncopyable;
 
}
  //  namespace boost

noncopyable的具体作用可以查考Effective C++第三版的Item6(若不想使用编译器自动产生的函数,就该明确拒绝)。
noncopyable本身并不复杂,当时引起我注意的是,noncopyable不是定义在boost名字空间,而是额外加多了个子空间noncopyable_,然后在boost名字空间用一个typedef导出(为了方便写boost::noncopyable而不需要写boost::noncopyable_::noncopyable,虽然前者其实是映射到后者上面)。

开始这不禁让我好奇这样的举动是否多次一举,但是注释protection from unintended ADL又告诉我们是为了防止无意识的ADL。经过思考,我得出的结论是:

我们先看一个noncopyable的典型的使用例子:
namespace   base
{
    
class Base : private boost::noncopyable
    
{
    }
;
}

namespace  derived
{
    
class Derived : public base::Base
    
{
    }
;
}

类Base通过私有继承至noncopyable来使得它自身是不可复制的,而子类Derived同样也如此。
如果noncopyable定义在boost名字空间,在涉及类Derived(以Derived类为参数)的非限定域的函数调用时,根据KL规则,编译器也会到boost名字空间去作名字查找(因为noncopyable是Derived的基类,即使是私有继承,但是访问规则检查是在重载决议之后起作用的),但是这时的KL根本就不是noncopyable所需要的。
首先因为boost是一个公开的大名字空间,容纳boost库里面的各种各样功能的类和模版,编译器可能会浪费很多时间在无意义的名字查找上,其次有时甚至可能会导致一些副作用,请看下面的例程:
#include  < iostream >
using   namespace  std;
 
namespace  boost
{
    
class noncopyable
    
{
    
protected:
         noncopyable() 
{}
         
~noncopyable() {}

    
private// emphasize the following members are private

         noncopyable( 
const noncopyable& );

         
const noncopyable& operator=const noncopyable& );
    }
;
 
    template
<typename T>
    
void foobar(const T&)
    
{
         cout
<<__FUNCTION__<<" : "<<__LINE__<<endl;
    }

}

 
namespace   base
{
    
class Base : private boost::noncopyable
    
{
    }
;
 
    
void foobar(const Base&)
    
{
         cout
<<__FUNCTION__<<" : "<<__LINE__<<endl;
    }

}

 
namespace  derived
{
    
class Derived : public base::Base
    
{
    }
;
 
    
void foobar(const Derived&)
    
{
         cout
<<__FUNCTION__<<" : "<<__LINE__<<endl;
    }

}

 
 
int  main()
{
    derived::Derived d;
    foobar(d);
 
    
char c; cin>>c;
    
return 0;
}
;

我们把noncopyable移到了boost名字空间,并假设boost库里面有一个void foobar(const T&)的模版函数在我们编译单元的可视范围内,但是我们对此一无所知。而刚好我们又用foobar这个名字定义了两个函数,一个作用于类Base,另外一个作用于类Derived,那么在main函数里面的foobar(d)发生了什么?程式输出derived::foorbar : 43,嗯,正如我们期望的一样,调用了void foobar(const Derived&),boost库里面的foobar模版函数并没有造成困扰,因为我们有最匹配的版本,foobar模版并没有被实例化。

但是假设我们没有定义 void foobar(const Derived&),因为我们期望使用类Base的foobar来处理所有从类Base派生出来的子类。当我们把上面代码的 void foobar(const Derived&)定义注释掉,在main里的调用是我们所期望的 void foobar(const Base&)吗?结果可能会让人大吃一惊,实际上是boost名字空间的foobar模版被实例化,使用的类型参数是类Derived,因为实例化的结果相比 void foobar(const Base&)更匹配,所以在重载决议中会被选为最合适的候选者。输出的结果是boost::foobar 19!
所以把noncopyable定义在一个小的私有的内层名字空间noncopyable_中,并通过typedef导出到boost名字空间,就可以在即不会导致任何不便的情况下,使得编译的速度更快,也不会有任何让人惊讶的结果。
试试下面修改后的代码,这时应该和你开始所期望的那样了吧:
#include  < iostream >
using   namespace  std;
 
namespace  boost
{
    
namespace noncopyable_
    
{
         
class noncopyable
         
{
         
protected:

                 noncopyable() 
{}

                 
~noncopyable() {}

         
private// emphasize the following members are private

                 noncopyable( 
const noncopyable& );
                 
const noncopyable& operator=const noncopyable& );
         }
;
    }

 
    typedef noncopyable_::noncopyable noncopyable;
 
    template
<typename T>
    
void foobar(const T&)
    
{
         cout
<<__FUNCTION__<<" : "<<__LINE__<<endl;
    }

}

 
namespace   base
{
    
class Base : private boost::noncopyable
    
{
    }
;
 
    
void foobar(const Base&)
    
{
         cout
<<__FUNCTION__<<" : "<<__LINE__<<endl;
    }

}

 
namespace  derived
{
    
class Derived : public base::Base
    
{
    }
;
}

 
 
int  main()
{
    derived::Derived d;
    foobar(d);
 
    
char c; cin>>c;
    
return 0;
}
;
  相关解决方案