C++ 当 operator& 重载时,如何可靠地获取对象的地址?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6494591/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 20:15:34  来源:igfitidea点击:

How can I reliably get an object's address when operator& is overloaded?

c++c++11operator-overloadingmemory-address

提问by James McNellis

Consider the following program:

考虑以下程序:

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 :'( 
}

How do I get clyde's address?

我如何得到clyde的地址?

I'm looking for a solution that will work equally well for all types of objects. A C++03 solution would be nice, but I'm interested in C++11 solutions too. If possible, let's avoid any implementation-specific behavior.

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

I am aware of C++11's std::addressoffunction template, but am not interested in using it here: I'd like to understand how a Standard Library implementor might implement this function template.

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

采纳答案by Matthieu M.

Update:in C++11, one may use std::addressofinstead of boost::addressof.

更新:在 C++11 中,可以使用std::addressof代替boost::addressof.



Let us first copy the code from Boost, minus the compiler work around bits:

让我们首先从 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 );
}

What happens if we pass a reference to function?

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

Note: addressofcannot be used with a pointer to function

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

In C++ if void func();is declared, then funcis a reference to a function taking no argument and returning no result. This reference to a function can be trivially converted into a pointer to function -- from @Konstantin: According to 13.3.3.2 both T &and T *are indistinguishable for functions. The 1st one is an Identity conversion and the 2nd one is Function-to-Pointer conversion both having "Exact Match" rank (13.3.3.1.1 table 9).

在 C++ 中,如果void func();声明了,则func是对不带参数且不返回结果的函数的引用。这种对函数的引用可以简单地转换为指向函数的指针——来自@Konstantin:根据 13.3.3.2T &T *函数无法区分。第一个是身份转换,第二个是函数到指针的转换,两者都具有“完全匹配”等级(13.3.3.1.1 表 9)。

The reference to functionpass through addr_impl_ref, there is an ambiguity in the overload resolution for the choice of f, which is solved thanks to the dummy argument 0, which is an intfirst and could be promoted to a long(Integral Conversion).

对函数pass through的引用addr_impl_ref,在重载决议中对于 的选择存在歧义f,这要归功于虚拟参数0,这是int第一个并且可以提升为long(积分转换)。

Thus we simply returns the pointer.

因此我们简单地返回指针。

What happens if we pass a type with a conversion operator ?

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

If the conversion operator yields a T*then we have an ambiguity: for f(T&,long)an Integral Promotion is required for the second argument while for f(T*,int)the conversion operator is called on the first (thanks to @litb)

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

That's when addr_impl_refkicks in. The C++ Standard mandates that a conversion sequence may contain at most one user-defined conversion. By wrapping the type in addr_impl_refand forcing the use of a conversion sequence already, we "disable" any conversion operator that the type comes with.

这就是开始的时候addr_impl_ref。C++ 标准要求转换序列最多可以包含一个用户定义的转换。通过包装类型addr_impl_ref并强制使用转换序列,我们“禁用”了该类型附带的任何转换运算符。

Thus the f(T&,long)overload is selected (and the Integral Promotion performed).

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

What happens for any other type ?

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

Thus the f(T&,long)overload is selected, because there the type does not match the T*parameter.

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

Note: from the remarks in the file regarding Borland compatibility, arrays do not decay to pointers, but are passed by reference.

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

What happens in this overload ?

在这种超载中会发生什么?

We want to avoid applying operator&to the type, as it may have been overloaded.

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

The Standard guarantees that reinterpret_castmay be used for this work (see @Matteo Italia's answer: 5.2.10/10).

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

Boost adds some niceties with constand volatilequalifiers to avoid compiler warnings (and properly use a const_castto remove them).

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

  • Cast T&to char const volatile&
  • Strip the constand volatile
  • Apply the &operator to take the address
  • Cast back to a T*
  • 投射T&char const volatile&
  • 剥离constvolatile
  • 应用&运算符取地址
  • 投射回一个 T*

The const/volatilejuggling is a bit of black magic, but it does simplify the work (rather than providing 4 overloads). Note that since Tis unqualified, if we pass a ghost const&, then T*is ghost const*, thus the qualifiers have not really been lost.

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

EDIT:the pointer overload is used for pointer to functions, I amended the above explanation somewhat. I still do not understand why it is necessarythough.

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

The following ideone outputsums this up, somewhat.

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

回答by Konrad Rudolph

Use std::addressof.

使用std::addressof.

You can think of it as doing the following behind the scenes:

您可以将其视为在幕后执行以下操作:

  1. Reinterpret the object as a reference-to-char
  2. Take the address of that (won't call the overload)
  3. Cast the pointer back to a pointer of your type.
  1. 将对象重新解释为对字符的引用
  2. 取那个地址(不会调用重载)
  3. 将指针转换回您类型的指针。

Existing implementations (including Boost.Addressof) do exactly that, just taking additional care of constand volatilequalification.

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

回答by Matteo Italia

The trick behind boost::addressofand the implementation provided by @Luc Danton relies on the magic of the reinterpret_cast; the standard explicitly states at §5.2.10 ?10 that

背后的技巧boost::addressof和@Luc Danton 提供的实现依赖于reinterpret_cast; 该标准在 §5.2.10 ?10 中明确指出

An lvalue expression of type T1can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. That is, a reference cast reinterpret_cast<T&>(x)has the same effect as the conversion *reinterpret_cast<T*>(&x)with the built-in &and *operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type.

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

Now, this allows us to convert an arbitrary object reference to a char &(with a cv qualification if the reference is cv-qualified), because any pointer can be converted to a (possibly cv-qualified) char *. Now that we have a char &, the operator overloading on the object is no longer relevant, and we can obtain the address with the builtin &operator.

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

The boost implementation adds a few steps to work with cv-qualified objects: the first reinterpret_castis done to const volatile char &, otherwise a plain char &cast wouldn't work for constand/or volatilereferences (reinterpret_castcannot remove const). Then the constand volatileis removed with const_cast, the address is taken with &, and a final reinterpet_castto the "correct" type is done.

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

The const_castis needed to remove the const/volatilethat could have been added to non-const/volatile references, but it does not "harm" what was a const/volatilereference in first place, because the final reinterpret_castwill re-add the cv-qualification if it was there in first place (reinterpret_castcannot remove the constbut can add it).

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

As for the rest of the code in addressof.hpp, it seems that most of it is for workarounds. The static inline T * f( T * v, int )seems to be needed only for the Borland compiler, but its presence introduces the need for addr_impl_ref, otherwise pointer types would be caught by this second overload.

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

Edit: the various overloads have a different function, see @Matthieu M. excellent answer.

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

Well, I'm no longer sure of this either; I should further investigate that code, but now I'm cooking dinner :) , I'll have a look at it later.

好吧,我也不再确定了。我应该进一步调查该代码,但现在我正在做饭 :) ,我稍后再看看。

回答by Luc Danton

I've seen an implementation of addressofdo this:

我见过addressof这样做的实现:

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

Don't ask me how conforming this is!

不要问我这有多合规!

回答by Konstantin Oznobihin

Take a look at boost::addressofand its implementation.

看看boost::addressof及其实现。