当操作符&重载时,如何可靠地获取对象的地址?

考虑下面的程序:

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增加了constvolatile限定符的一些细节,以避免编译器警告(并正确使用const_cast去除它们)。

  • Cast T& char const volatile&
  • 去掉constvolatile
  • 使用&运算符来获取地址
  • 投回到T*

const / volatile杂耍有一点魔力,但它简化了工作(而不是提供4个重载)。 请注意,因为T是不合格的,所以如果我们传递一个ghost const& ,那么T*就是ghost const* ,所以限定符还没有真正丢失。

编辑:指针重载是用于指针function,我修改了上述解释一些。 我仍然不明白为什么有必要

下面的ideone输出结果总结了一些。

本质上,你可以重新解释对象作为引用到字符,取其地址(不会调用重载),并将指针转换回你的types的指针。

Boost.AddressOf的代码就是这样做的,只是额外的关心volatileconst限定。

boost::addressof背后的窍门和@Luc Danton提供的实现依赖于reinterpret_cast的魔力; 该标准在§5.2.10¶10中明确指出

如果可以使用reinterpret_cast将types为“ T1指针”的expression式明确地转换为types“指向T2指针”,则可以将typesT1的左值expression式转换为types“对T2引用”。 也就是说,引用cast reinterpret_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删除constvolatile ,用&取地址,最后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及其实现。