当操作符&重载时,如何可靠地获取对象的地址?
考虑下面的程序:
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及其实现。