当操作符&重载时,如何可靠地获取对象的地址?
考虑下面的程序:
struct ghost { // ghosts like to pretend that they don't exist ghost* operator&() const volatile { return 0; } }; int main() { ghost clyde; ghost* clydes_address = &clyde; // darn; that's not clyde's address :'( }
我如何得到clyde的地址?
我正在寻找一种解决scheme,将同样适用于所有types的对象。 一个C ++ 03解决scheme会很好,但我也对C ++ 11解决scheme感兴趣。 如果可能的话,让我们避免任何特定于实现的行为。
我知道C ++ 11的std::addressof函数模板,但我不想在这里使用它:我想了解一个标准库实现者可能如何实现这个函数模板。
更新:在C ++ 11中,可以使用std::addressof而不是boost::addressof 。
让我们先从Boost中复制代码,然后减去编译器的工作:
template<class T> struct addr_impl_ref { T & v_; inline addr_impl_ref( T & v ): v_( v ) {} inline operator T& () const { return v_; } private: addr_impl_ref & operator=(const addr_impl_ref &); }; template<class T> struct addressof_impl { static inline T * f( T & v, long ) { return reinterpret_cast<T*>( &const_cast<char&>(reinterpret_cast<const volatile char &>(v))); } static inline T * f( T * v, int ) { return v; } }; template<class T> T * addressof( T & v ) { return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 ); }
如果我们传递一个函数的参考会发生什么?
注意: addressof不能和指向函数的指针一起使用
在C ++中,如果void func(); 是声明的,那么func是一个不带参数的函数的引用,不返回任何结果。 对函数的这种引用可以简单地转换成函数的指针 – 从@Konstantin :根据13.3.3.2, T &和T *对函数都是无法区分的。 第一个是标识转换,第二个是“精确匹配”等级(13.3.3.1.1表9)的函数到指针的转换。
对函数的引用通过了addr_impl_ref ,在重载决议中对于f的select有一个模棱两可的问题,这是由于虚参数0 ,它是一个int首先可以被提升为一个long (积分转换)来解决的。
因此,我们只是返回指针。
如果我们通过一个转换运算符的types会发生什么?
如果转换运算符产生一个T*那么我们有一个含糊之处:对于f(T&,long) ,第二个参数需要整体提升,而对于f(T*,int)则转换运算符是第一个被调用的@litb)
这就是addr_impl_ref启动的时候addr_impl_ref ++标准规定转换序列最多可以包含一个用户定义的转换。 通过在addr_impl_ref包装types并强制使用转换序列,我们“禁用”该types随附的任何转换运算符。
因此,selectf(T&,long)超载(并且执行整体升级)。
其他types会发生什么?
因此,selectf(T&,long)过载,因为那里的types与T*参数不匹配。
注意:从文件中关于Borland兼容性的注释中,数组不会衰减到指针,而是通过引用传递。
这个超载会发生什么?
我们希望避免将operator&应用于types,因为它可能已经超载。
标准保证reinterpret_cast可以用于这项工作(参见@Matteo Italia的答案:5.2.10 / 10)。
Boost增加了const和volatile限定符的一些细节,以避免编译器警告(并正确使用const_cast去除它们)。
- Cast
T&char const volatile& - 去掉
const和volatile - 使用
&运算符来获取地址 - 投回到
T*
const / volatile杂耍有一点魔力,但它简化了工作(而不是提供4个重载)。 请注意,因为T是不合格的,所以如果我们传递一个ghost const& ,那么T*就是ghost const* ,所以限定符还没有真正丢失。
编辑:指针重载是用于指针function,我修改了上述解释一些。 我仍然不明白为什么有必要 。
下面的ideone输出结果总结了一些。
本质上,你可以重新解释对象作为引用到字符,取其地址(不会调用重载),并将指针转换回你的types的指针。
Boost.AddressOf的代码就是这样做的,只是额外的关心volatile和const限定。
boost::addressof背后的窍门和@Luc Danton提供的实现依赖于reinterpret_cast的魔力; 该标准在§5.2.10¶10中明确指出
如果可以使用
reinterpret_cast将types为“T1指针”的expression式明确地转换为types“指向T2指针”,则可以将typesT1的左值expression式转换为types“对T2引用”。 也就是说,引用castreinterpret_cast<T&>(x)*reinterpret_cast<T*>(&x)与内置的&和*运算符转换*reinterpret_cast<T*>(&x)具有相同的效果。 结果是一个左值,指向与源左值相同的对象,但具有不同的types。
现在,这允许我们将任意对象引用转换为char & (如果引用是cv限定的,则使用cv限定),因为任何指针都可以转换为(可能是cv限定的) char * 。 现在我们已经有了一个char & ,对象上的操作符重载不再相关,我们可以用内build的&操作符来获取地址。
boost实现添加了几个步骤来处理cv-qualified对象:第一个reinterpret_cast用于const volatile char & ,否则一个普通的char & cast不适用于const和/或volatile引用( reinterpret_cast不能删除const )。 然后用const_cast删除const和volatile ,用&取地址,最后reinterpet_cast为“correct”types。
需要const_cast来移除可能已经添加到非const / volatile引用的const / volatile,但是它并不“损害” const / volatile引用,因为最后的reinterpret_cast会重新添加cv-qualification如果它在那里( reinterpret_cast无法删除const但可以添加它)。
至于 addressof.hpp的其他代码,似乎大部分是用于解决方法。static inline T * f( T * v, int )似乎只用于Borland编译器,但它的存在引入了对addr_impl_ref的需要,否则指针types将被第二次重载捕获。
编辑 :各种重载有不同的function,请参阅@Matthieu M.优秀的答案 。
那么,我也不能确定这一点。 我应该进一步调查这个代码,但现在我正在做晚餐:),我稍后再看看。
我已经看到了这样做的addressof的实现:
char* start = &reinterpret_cast<char&>(clyde); ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);
不要问我这是多么符合!
看看boost :: addressof及其实现。