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

Jam*_*lis 167 c++ operator-overloading memory-address c++11

考虑以下程序:

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 :'( 
}
Run Code Online (Sandbox Code Playgroud)

我怎么得到clyde地址?

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

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

Mat*_* M. 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 );
}
Run Code Online (Sandbox Code Playgroud)

如果我们传递对函数引用会发生什么?

注意:addressof不能与指向函数的指针一起使用

在C++ void func();中声明,然后func是对不带参数且不返回结果的函数的引用.这个对函数的引用可以简单地转换为指向函数的指针 - 来自@Konstantin:根据13.3.3.2两者T &并且T *对于函数是无法区分的.第一个是身份转换,第二个是具有"精确匹配"等级的功能到指针转换(13.3.3.1.1表9).

对函数引用通过addr_impl_ref,选择的重载决策存在歧义f,这可以通过伪参数来解决,伪参数0int第一个,可以提升为long(积分转换).

因此我们只返回指针.

如果我们传递带转换运算符的类型会发生什么?

如果转换运算符产生a,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限定符以避免编译器警告(并正确使用a const_cast来删除它们).

  • 铸造T&char const volatile&
  • 去除constvolatile
  • 应用&运营商获取地址
  • 回到一个 T*

const/ volatile杂耍有点黑魔法,但它确实简化工作(而不是提供4个重载).需要注意的是,由于T是不合格的,如果我们过了ghost const&,然后T*就是ghost const*,这样的预选赛还没有真正丢失.

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

以下ideone输出在某种程度上总结了这一点.

  • 为什么需要解决具有转换功能的类型?是不是更喜欢完全匹配调用任何转换函数到'T*`?编辑:现在我明白了.它会,但是使用'0`参数,它会以*纵横交错*结束,因此会有歧义. (3认同)
  • "如果我们传递一个指针会怎么样?" 部分不正确.如果我们将指针传递给某个类型U的函数地址,则类型'T'被推断为'U*',而addr_impl_ref将有两个重载:'f(U*&,long)'和'f(U**, int)',显然第一个将被选中. (2认同)
  • @Johannes:[克里斯 - 克罗斯?](http://www.youtube.com/watch?v=VMkIuKXwmlU) (2认同)

Kon*_*lph 97

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

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

  • 我喜欢这个解释比选择的更好,因为它可以很容易理解. (16认同)

Mat*_*lia 49

boost::addressof@Luc Danton提供的诀窍和实施依赖于魔术reinterpret_cast; 该标准明确规定§5.2.1010

如果类型"指向" 的表达式可以使用a 显式转换为"指向" 的类型,则T1可以将类型的左值表达式强制转换为"引用T2"类型.也就是说,参考演员与使用内置和运算符的转换具有相同的效果.结果是一个左值,它引用与源左值相同的对象,但具有不同的类型.T1T2reinterpret_castreinterpret_cast<T&>(x)*reinterpret_cast<T*>(&x)&*

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

boost实现添加了几个步骤来处理cv限定的对象:第一个reinterpret_cast是完成的const volatile char &,否则一个普通的char &强制转换不适用于const和/或volatile引用(reinterpret_cast不能删除const).然后constvolatile与删除const_cast,地址被采取&,并最终reinterpet_cast以"正确"的类型完成.

const_cast需要删除const/ volatile可能已被添加到非const /易失性的参考,但它没有"伤害"什么是const/ volatile排在首位的参考,因为最终reinterpret_cast会重新添加CV-资格,如果它是在第一位(reinterpret_cast不能删除const但可以添加它).

至于代码的其余部分addressof.hpp,似乎大多数代码都是为了解决方法.在static inline T * f( T * v, int )似乎只需要在Borland编译,但它的出现引入了必要addr_impl_ref的,否则指针类型将通过本次超载被抓.

编辑:各种重载都有不同的功能,请参阅 @Matthieu M.优秀答案.

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

  • @curiousguy:你为什么认为应该这样做?我已经引用了特定的 C++ 标准部分,这些部分规定了编译器应该做什么,并且我可以访问的所有编译器(包括但不限于 gcc 4.3.4、comeau-online、VC6.0-VC2010)报告歧义,正如我所描述的那样。您能否详细说明您对本案的推理? (2认同)

Luc*_*ton 11

我已经看到了addressof这样做的实现:

char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);
Run Code Online (Sandbox Code Playgroud)

不要问我这是多么的顺从!

  • @DeadMG我不是说这不合规.我说你不应该问我:) (6认同)
  • 法律.`char*`是列出别名规则的列出异常. (5认同)
  • @DeadMG 这里没有别名问题。问题是:“reinterpret_cast&lt;char*&gt;”定义是否明确。 (2认同)
  • @curiousguy,答案是肯定的,总是允许将任何指针类型转换为`[unsigned] char *`,从而读取指向对象的对象表示。这是 `char` 具有特殊权限的另一个区域。 (2认同)

Kon*_*hin 5

看一下boost :: addressof及其实现.