operator&がオーバーロードされているときに、オブジェクトのアドレスを確実に取得するにはどうすればよいですか?


170

次のプログラムを検討してください。

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関数テンプレートは知っていますが、ここでは使用しません。標準ライブラリの実装者がこの関数テンプレートを実装する方法を知りたいのですが。


41
@jalf:その戦略は受け入れられますが、私は言った個人を頭の中で殴ったので、どうすれば彼らの忌まわしいコードを回避できますか?:-)
James McNellis、2011年

5
@jalfええと、この演算子をオーバーロードしてプロキシオブジェクトを返す必要がある場合があります。今のところ例は思いつきませんが。
Konrad Rudolph

5
@Konrad:私も。それが必要な場合は、演算子をオーバーロードすると問題が多すぎるため、設計を再検討することをお勧めします。:)
jalf

2
@Konrad:C ++プログラミングの約20年で、私はかつてその演算子をオーバーロードしようとしました。それがその20年の最初の頃でした。ああ、私はそれを使用可能にすることに失敗しました。その結果、演算子のオーバーロードFAQエントリは、「単項アドレス演算子は決してオーバーロードされるべきではない」と述べています。このオペレーターをオーバーロードするための説得力のある例を思い付くことができれば、次回会うときに無料のビールが手に入ります。(私はあなたがベルリンを去るのを知っているので、私はこれを安全に提供することができます:)
sbi

5
CComPtr<>そして、CComQIPtr<>過負荷に持ってoperator&
サイモン・リヒター

回答:


102

更新: 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 ++では、if void func();が宣言されている場合、funcは引数を取らず、結果を返さない関数への参照です。関数へのこの参照は、関数へのポインタに簡単に変換できます@Konstantin-from:13.3.3.2によると両方ともT &T *関数と区別できません。1つ目はID変換で、2つ目は「完全一致」ランク(13.3.3.1.1表9)を持つ関数からポインターへの変換です。

関数 pass through 参照addr_impl_refは、の選択に対するオーバーロードの解決にあいまいさがあり、これは最初であり、(Integral Conversion)に昇格できるfダミー引数のおかげで解決されます。0intlong

したがって、単純にポインタを返します。

変換演算子を使用して型を渡すとどうなりますか?

変換演算子がa T*を生成する場合、あいまいさf(T&,long)があります。f(T*,int)最初の引数で変換演算子が呼び出されている間、2番目の引数には統合プロモーションが必要です(@litbに感謝)

それがaddr_impl_ref始まるときです。C++標準では、変換シーケンスには最大で1つのユーザー定義の変換が含まれるように規定されています。型をラップしaddr_impl_ref、変換シーケンスの使用を強制することにより、型に付属する変換演算子をすべて「無効」にします。

したがって、f(T&,long)過負荷が選択されます(および統合プロモーションが実行されます)。

他のタイプの場合はどうなりますか?

したがって、f(T&,long)タイプがT*パラメータと一致しないため、オーバーロードが選択されます。

注:Borlandの互換性に関するファイル内のコメントから、配列はポインターに減衰しませんが、参照によって渡されます。

このオーバーロードで何が起こりますか?

operator&型が過負荷になっている可能性があるため、型には適用しないでください。

reinterpret_castこの作業に使用できる標準が保証されています(@Matteo Italiaの回答:5.2.10 / 10を参照)。

Boostは、コンパイラの警告を回避するための修飾子constといくつかの便利な機能を追加しvolatileます(そしてconst_castそれらを削除するために適切にa を使用します)。

  • キャストT&するchar const volatile&
  • ストリップconstvolatile
  • &オペレーターに住所を申請する
  • にキャストバックする T*

const/ volatileジャグリングは黒魔術のビットであり、それは(かなり4つのオーバーロードを提供するよりも)作業を簡素化しません。以来という注意T修飾されていない、私たちが渡した場合はghost const&、その後T*であるghost const*ので、修飾子は本当に失われていません、。

編集:ポインターオーバーロードは関数へのポインターに使用されます。上記の説明を多少修正しました。なぜそれが必要なのかはまだわかりません。

次のideone出力は、これをいくらか要約しています。


2
「ポインタを渡すとどうなりますか?」一部が間違っています。あるタイプUへのポインターを渡す場合、関数のアドレス 'T'は 'U *'であると推測され、addr_impl_refには 'f(U *&、long)'と 'f(U **、 int) '、明らかに最初のものが選択されます。
コンスタンティンオズノビヒン2011年

@Konstantin:そう、私は2つのfオーバーロードが関数テンプレートであると思っていましたが、それらはテンプレートクラスの通常のメンバー関数です。(ここで、オーバーロードの使用方法を理解する必要があります。ヒントはありますか?)
Matthieu M.

これは、よく説明されたすばらしい答えです。「キャストスルー」だけではなく、これ以上のことがあると思いましたchar*。マシュー、ありがとう。
James McNellis、2011年

@ジェームズ:私がミスをしたときはいつでも棒で頭を叩く@コンスタンチンから多くの助けを受けました:D
マシューM.

3
変換関数を持つ型を回避する必要があるのはなぜですか?変換関数を呼び出すよりも完全一致を優先するのではないでしょうT*か?編集:なるほど。それは可能ですが、0議論によってそれは十字形になってしまうため、あいまいになります。
Johannes Schaub-litb

99

を使用しstd::addressofます。

これは、舞台裏で次のことを行うと考えることができます。

  1. オブジェクトをcharへの参照として再解釈します
  2. そのアドレスを取る(オーバーロードを呼び出さない)
  3. ポインタを自分のタイプのポインタにキャストします。

既存の実装(Boost.Addressofを含む)はまさにそれを行い、追加の注意constvolatile資格を取得します。


16
この説明は、理解しやすいので、選んだものよりも好きです。
そり

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)組み込み&および*演算子による変換と同じ効果があります。結果は、ソースlvalueと同じオブジェクトを参照するlvalueですが、型が異なります。

これにより、任意のオブジェクト参照をchar &(参照がcv修飾されている場合はcv修飾を使用して)aに変換できます。これは、ポインターを(おそらくcv修飾された)に変換できるためchar *です。これでがあるchar &ので、オブジェクトのオーバーロード演算子は関係なくなり、組み込み&演算子を使用してアドレスを取得できます。

ブーストの実装では、cv修飾オブジェクトを処理するためのいくつかのステップが追加されます。最初のステップreinterpret_castはに行われますconst volatile char &。そうでない場合、プレーンchar &キャストは参照constvolatile参照に対して機能しreinterpret_castません(削除できませんconst)。次に、constおよびvolatileで削除され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.の優れた回答を参照してください。

まあ、もうこれもよくわかりません。そのコードをさらに調査する必要がありますが、今は夕食を調理しています:)、後で見ていきます。


Mattofeu M.は、addressofへのポインタの受け渡しに関する説明が間違っています。そのような編集であなたの素晴らしい答えを台無しにしないでください:)
Konstantin Oznobihin

「良いappetit」、さらなる調査は、過負荷が関数への参照のために呼び出されることを示していますvoid func(); boost::addressof(func);。ただし、オーバーロードを削除しても、gcc 4.3.4がコードをコンパイルして同じ出力を生成することは妨げられないため、このオーバーロードが必要な理由がまだわかりません。
Matthieu M.11年

@Matthieu:gccのバグのようです。13.3.3.2によれば、T&とT *はどちらも関数を区別できません。1つ目はID変換で、2つ目は「完全一致」ランク(13.3.3.1.1表9)を持つ関数からポインターへの変換です。したがって、追加の引数を持つ必要があります。
コンスタンティンオズノビヒン2011年

@Matthieu:gcc 4.3.4(ideone.com/2f34P)で試してみたところ、予想どおりにあいまいになった。addressofの実装や無料の関数テンプレートなど、オーバーロードされたメンバー関数を試しましたか?後者(ideone.com/vjCRsなど)は、テンプレート引数の演繹規則(14.8.2.1/2)により、「T *」オーバーロードが選択されます。
コンスタンティンオズノビヒン

2
@curiousguy:なぜそうすべきだと思いますか?コンパイラーが何をすべきかを規定する特定のC ++標準パーツと、私がアクセスできるすべてのコンパイラー(gcc 4.3.4、comeau-online、VC6.0-VC2010を含むがこれらに限定されない)を参照して、説明したようにあいまいさを報告しました。このケースに関するあなたの推論を詳しく説明していただけませんか?
コンスタンティンオズノビヒン2011

11

addressofはこれを実装するのを見てきました:

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

これがどれだけ順応しているか私に尋ねないでください!


5
法的。char*タイプエイリアスルールの例外としてリストされています。
子犬'27年

6
@DeadMG私はこれが準拠していないとは言っていません。私はあなたが私に尋ねるべきではないと言っています:)
Luc Danton

1
@DeadMGここではエイリアシングの問題はありません。問題は、reinterpret_cast<char*>明確に定義されていることです。
curiousguy

2
@curiousguyと答えは「はい」です。ポインター型をキャストし[unsigned] char *、それによって、ポイントされたオブジェクトのオブジェクト表現を読み取ることが常に許可されます。これはchar特別な特権を持つ別のエリアです。
underscore_d

@underscore_dキャストが「常に許可されている」からといって、キャストの結果を使用して何でもできるというわけではありません。
curiousguy

5

boost :: addressofとその実装を見てください。


1
ブーストコードは興味深いものですが、その手法の説明はありません(2つのオーバーロードが必要な理由も説明されていません)。
James McNellis、2011年

「静的インラインT * f(T * v、int)」オーバーロードを意味しますか?Borland Cの回避策にのみ必要なようです。そこで使用されるアプローチは非常に簡単です。「T&」から「char&」への変換が唯一の微妙な(非標準)ものです。標準ではありますが、 'T *'から 'char *'へのキャストが許可されているため、参照キャストにはそのような要件はないようです。それでも、ほとんどのコンパイラでまったく同じように機能することが期待できます。
コンスタンティンオズノビヒン2011年

@Konstantin:ポインターの場合addressof、ポインター自体を返すため、オーバーロードが使用されます。それがユーザーが望んでいたものかどうかは議論の余地がありますが、それが指定した方法です。
Matthieu M.11年

@Matthieu:よろしいですか?私が知る限り、すべての型(ポインター型を含む)は内にラップされているaddr_impl_refため、ポインターオーバーロードを呼び出さないでください...
Matteo Italia

1
@KonstantinOznobihinこれは実際には質問に答えるものではありません。答え何であるかではなく、どこで答え探すかということです
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.