考虑以下计划:
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
函数模板,但我不想在此处使用它:我想了解标准库实现者如何实现此函数模板。
答案 0 :(得分:98)
更新:,可以使用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_cast
和const
限定符添加了一些细节,以避免编译器警告(并正确使用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代码就是这样做的,只需要额外注意volatile
和const
资格。
答案 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
移除const
和volatile
,地址与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及其实施情况。