如何在操作员和操作员处可靠地获取对象的地址?超载了吗?

时间:2011-06-27 14:39:04

标签: c++ c++11 operator-overloading memory-address

考虑以下计划:

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函数模板,但我不想在此处使用它:我想了解标准库实现者如何实现此函数模板。

5 个答案:

答案 0 :(得分:98)

在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的重载决策存在歧义,这可以通过伪参数{{ 1}},首先是0,可以升级为int(积分转换)。

因此我们只返回指针。

  

如果我们传递带转化运算符的类型会怎样?

如果转化运算符产生long,那么我们就会产生歧义:对于T*,第二个参数需要积分促销,而对于f(T&,long),转换运算符在第一个参数上调用(感谢@litb)

f(T*,int)启动时.C ++标准规定转换序列最多可包含一个用户定义的转换。通过将类型包装在addr_impl_ref中并强制使用转换序列,我们会“禁用”该类型附带的任何转换运算符。

因此选择addr_impl_ref重载(并执行积分促销)。

  

其他任何类型会发生什么?

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

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

  

这次重载会发生什么?

我们希望避免将T*应用于该类型,因为它可能已经过载。

标准保证operator&可用于此项工作(参见@Matteo Italia的回答:5.2.10 / 10)。

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

  • const_cast投放到T&
  • 剥离char const volatile&const
  • 应用volatile运算符获取地址
  • 转回&

T* / const杂耍是一种黑魔法,但它确实简化了工作(而不是提供4次重载)。请注意,由于volatile不合格,如果我们传递T,则ghost const&T*,因此限定词并未真正丢失。

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

以下ideone output对此进行了总结。

答案 1 :(得分:96)

基本上,您可以将对象重新解释为char-reference-reference,获取其地址(不会调用重载)并将指针强制转换为类型的指针。

Boost.AddressOf代码就是这样做的,只需要额外注意volatileconst资格。

答案 2 :(得分:49)

boost::addressof背后的技巧和@Luc Danton提供的实现依赖于reinterpret_cast的魔力;标准明确规定§5.2.10¶10

  

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

现在,这允许我们将任意对象引用转换为char *(如果引用是cv限定的,则具有cv限定条件),因为任何指针都可以转换为(可能是cv限定的){ {1}}。现在我们有char &,对象上的运算符重载不再相关,我们可以使用内置&运算符获取地址。

boost实现添加了几个步骤来处理cv限定对象:第一个reinterpret_cast完成const volatile char &,否则普通char &强制转换不适用于{{ 1}}和/或const引用(volatile无法移除reinterpret_cast)。然后使用const移除constvolatile,地址与const_cast一起使用,最终&到“正确”类型。

需要reinterpet_cast才能删除可能已添加到非const / volatile引用中的const_cast / const,但它不会“伤害”volatile / const首先是1}} / volatile引用,因为最终reinterpret_cast会重新添加cv资格(如果它位于第一位)reinterpret_cast无法删除const但是可以添加它。)

至于addressof.hpp中的其余代码,似乎大多数代码都是为了解决方法。似乎仅对Borland编译器需要static inline T * f( T * v, int ),但它的存在引入了对addr_impl_ref的需求,否则指针类型将被第二次重载捕获。

编辑 :各种重载功能不同,请参阅@Matthieu M. excellent answer

嗯,我也不再确定这一点;我应该进一步调查这段代码,但现在我正在做晚餐:),我稍后会看一下。

答案 3 :(得分:11)

我已经看到addressof的实现执行此操作:

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

不要问我这是多么顺从!

答案 4 :(得分:5)

查看boost::addressof及其实施情况。