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类源码时的一些疑问:
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
// 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
{
};
}
{
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;
} ;
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;
} ;
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;
} ;