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

考虑以下方案:

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的地址?

我正在寻找一个解决方案,将同样适用于所有类型的对象。一个 C + + 03解决方案会很好,但是我也对 C + + 11解决方案感兴趣。如果可能的话,让我们避免任何特定于实现的行为。

我知道 C + + 11的 std::addressof函数模板,但对在这里使用它不感兴趣: 我想了解标准库实现者如何实现这个函数模板。

23182 次浏览

看看 地址及其实现。

我见过 addressof的一个实现这样做:

char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);

别问我这有多符合!

使用 std::addressof

你可以把它想象成在幕后做以下事情:

  1. 将对象重新解释为对 char 的引用
  2. 采取的地址(不会调用超载)
  3. 将指针抛回到类型的指针。

现有的实现(包括 Boost. Addressof)正是这样做的,只是额外注意了 constvolatile的资格。

boost::addressof背后的技巧和@Luc Danton 提供的实现依赖于 reinterpret_cast的魔力; 标准在5.2.1010明确指出

如果可以使用 reinterpret_cast显式地将类型为“指向 T1”的表达式转换为类型为“指向 T2”的表达式,则 T1类型的左值表达式可以转换为类型为“ reference to T2”。也就是说,引用强制转换 reinterpret_cast<T&>(x)与具有内置 &*运算符的转换 *reinterpret_cast<T*>(&x)具有相同的效果。结果是一个 lvalue,它引用与源 lvalue 相同的对象,但是具有不同的类型。

现在,这允许我们将任意对象引用转换为 char &(如果引用是 cv 限定的,则使用 cv 限定) ,因为任何指针都可以转换为(可能是 cv 限定的) char *。现在我们有了一个 char &,对象上的运算符重载就不再相关了,我们可以通过内置的 &操作符获得地址。

升级实现添加了一些步骤来处理 cv 限定的对象: 第一个 reinterpret_cast是对 const volatile char &进行的,否则纯 char &强制转换对 const和/或 volatile引用不起作用(reinterpret_cast不能删除 const)。然后用 const_cast删除 constvolatile,用 const volatile char &0获取地址,最后用“正确”类型获取 const volatile char &1。

需要 const_cast来删除可能被添加到非常数/易失性引用中的 const/volatile,但是它不会“损害”首先是 const/volatile引用的内容,因为最终的 reinterpret_cast将重新添加 cv 限定,如果它首先存在的话(reinterpret_cast不能删除 const,但可以添加它)。

至于 addressof.hpp中的其余代码,似乎大部分都是用于变通方法。似乎只有 Borland 编译器需要 static inline T * f( T * v, int ),但是它的出现引入了对 addr_impl_ref的需求,否则指针类型将被第二次重载捕获。

编辑 : 各种重载具有不同的功能,请参见 @ 马修 M 答得好

好吧,我也不再确定这一点; 我应该进一步研究这段代码,但现在我正在做晚饭:) ,稍后我会看一下它。

更新: 在 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的过载分辨率中有一个模糊的地方,这是由于虚拟参数 0解决的,它首先是一个 int,并且可以提升到一个 long(积分转换)。

因此,我们只是返回指针。

如果我们用转换运算符传递一个类型会发生什么?

如果转换运算符产生一个 T*,那么我们就有了一个模糊性: 对于 f(T&,long),第二个参数需要积分提升,而对于 f(T*,int),在第一个 (感谢@litb)上调用转换运算符

这时 addr_impl_ref开始发挥作用。C + + 标准规定转换序列最多只能包含一个用户定义的转换。通过在 addr_impl_ref中包装类型并强制已经使用转换序列,我们“禁用”该类型附带的任何转换运算符。

这样就选择了 f(T&,long)过载(并执行了积分提升)。

其他类型会发生什么?

因此选择了 f(T&,long)重载,因为类型与 T*参数不匹配。

注意: 从文件中关于 Borland 兼容性的备注中可以看出,数组不会衰减为指针,而是通过引用传递。

这个过载会发生什么?

我们希望避免对类型应用 operator&,因为它可能已经被重载。

标准保证 reinterpret_cast可以用于这项工作(见@Matteo Italia 的回答: 5.2.10/10)。

Boost 使用 constvolatile限定符添加了一些细节,以避免编译器警告(并正确地使用 const_cast来删除它们)。

  • 播放 T&char const volatile&
  • 剥离 constvolatile
  • 应用 &操作符获取地址
  • 播放 T*

const/volatile杂耍是一个有点黑魔法,但它确实简化了工作(而不是提供4重载)。注意,由于 T是不限定的,如果我们通过一个 ghost const&,那么 T*就是 ghost const*,因此限定符并没有真正丢失。

编辑: 指针重载用于指向函数的指针,我稍微修改了上面的解释。我仍然不明白为什么它是 有需要虽然。

下面的 一个输出在一定程度上总结了这一点。