私はreinterpret_cast
とstatic_cast
の適用性について少し混乱しています。私が読んだことから、コンパイル時に型を解釈できるときは静的キャストを使うことが一般的な規則であり、それゆえWord static
です。これは、C++コンパイラが暗黙的キャストにも内部的に使用するキャストです。
reinterpret_cast
sは2つのシナリオ、整数型からポインタ型への変換、またはその逆の変換、あるいはあるポインタ型から別のポインタ型への変換に適用できます。私が得る一般的な考え方は、これは移植不可能であり、避けるべきです。
少し混乱しているのは、私が必要とする使い方の1つで、CからC++を呼び出しています。CコードはC++オブジェクトを保持する必要があるので、基本的にはvoid*
を保持しています。 void *
とClass型の間の変換に使用するキャストは何ですか?
static_cast
とreinterpret_cast
の両方の使い方を見たことがありますか?私が読んできたものから、キャストはコンパイル時に起こる可能性があるのでstatic
のほうが良いようですが?あるポインタ型から別のポインタ型に変換するためにreinterpret_cast
を使うことを言っていますが?
C++標準は次のことを保証しています。
static_cast
を出入りするポインタをvoid*
すると、アドレスが保持されます。つまり、以下では、a、b、cはすべて同じアドレスを指しています。
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
reinterpret_cast
は、ポインタを異なる型にキャストしてからreinterpret_cast
それを元の型に戻すと、元の値になることを保証するだけです。だから以下で:
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
aとcは同じ値を含みますが、bの値は指定されていません。 (実際には通常、aとcと同じアドレスを含みますが、標準では指定されていません。さらに複雑なメモリシステムを搭載したマシンでは正しくない場合があります。)
Void *との間でキャストする場合は、static_cast
が優先されます。
reinterpret_cast
が必要な場合の1つのケースは、不透明(OPAQUE)型とのインターフェース時です。これは、プログラマが制御できないベンダーAPIで頻繁に発生します。これは、ベンダーが任意のグローバルデータを格納および取得するためのAPIを提供するという人為的な例です。
// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();
このAPIを使用するには、プログラマーは自分のデータをVendorGlobalUserData
にキャストしてまた戻す必要があります。 static_cast
は動作しません、reinterpret_cast
を使わなければなりません:
// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;
struct MyUserData {
MyUserData() : m(42) {}
int m;
};
int main() {
MyUserData u;
// store global data
VendorGlobalUserData d1;
// d1 = &u; // compile error
// d1 = static_cast<VendorGlobalUserData>(&u); // compile error
d1 = reinterpret_cast<VendorGlobalUserData>(&u); // ok
VendorSetUserData(d1);
// do other stuff...
// retrieve global data
VendorGlobalUserData d2 = VendorGetUserData();
MyUserData * p = 0;
// p = d2; // compile error
// p = static_cast<MyUserData *>(d2); // compile error
p = reinterpret_cast<MyUserData *>(d2); // ok
if (p) { cout << p->m << endl; }
return 0;
}
以下はサンプルAPIの考案された実装です。
// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
簡単な答え:reinterpret_cast
の意味がわからない場合は、使用しないでください。あなたが将来それを必要とするならば、あなたは知っているでしょう。
全回答:
基本的な数値タイプを考えましょう。
たとえばint(12)
をunsigned float (12.0f)
に変換すると、両方の数値のビット表現が異なるため、プロセッサはいくつかの計算を呼び出す必要があります。これがstatic_cast
の意味です。
一方、reinterpret_cast
を呼び出すと、CPUは計算を呼び出しません。それはちょうどそれが別の型を持っているかのようにメモリ内のビットのセットを扱います。そのため、このキーワードを使用してint*
をfloat*
に変換すると、新しい値(ポインタ参照解除後)は、数学的な意味で古い値とは無関係になります。
例:reinterpret_cast
は、1つの理由で移植性がないことは事実です。バイト順(エンディアン) 。しかし、これは多くの場合驚くべきことにそれを使用するための最良の理由です。例を想像してみましょう:あなたはファイルからバイナリの32ビット数を読まなければならない、そしてあなたはそれがビッグエンディアンであることを知っている。あなたのコードは汎用的でなければならず、ビッグエンディアン(例えばARM)とリトルエンディアン(例えばx86)システムで適切に動作します。それで、あなたはバイトオーダーをチェックしなければなりません。 コンパイル時によく知られているので、 これを実現するための関数を書くことができます。constexpr
関数を書くことができます。
/*constexpr*/ bool is_little_endian() {
std::uint16_t x=0x0001;
auto p = reinterpret_cast<std::uint8_t*>(&x);
return *p != 0;
}
説明:メモリー内のx
のバイナリー表記は、0000'0000'0000'0001
(big)または0000'0001'0000'0000
(リトル・エンディアン)になります。 ) p
ポインタの下のバイトを再解釈キャストした後、それぞれ0000'0000
または0000'0001
になります。静的キャスティングを使用する場合、どのエンディアンが使用されていても、常に0000'0001
になります。
編集:
最初のバージョンでは、例関数is_little_endian
をconstexpr
にしました。最新のgcc(8.3.0)でうまくコンパイルされていますが、規格では違法であると言われています。 clangコンパイラはそれをコンパイルすることを拒否します(これは正しいです)
reinterpret_cast
の意味は、C++標準では定義されていません。したがって、理論的にはreinterpret_cast
はあなたのプログラムをクラッシュさせる可能性があります。実際には、コンパイラはあなたが期待していることをやろうとします。それはあなたが渡しているものをあたかもあなたがキャストしている型であるかのように解釈することです。あなたが使用しようとしているコンパイラがreinterpret_cast
で何をするのか知っているなら、あなたはそれを使うことができますが、それが移植性があると言うのはうそです。
あなたが説明する場合、そしてあなたがreinterpret_cast
を考えるかもしれないほとんどすべての場合のために、あなたは代わりにstatic_cast
あるいは他の何か代わりを使うことができます。他のものの間で規格はこれがあなたがstatic_cast
(5.2.9)に何が期待できるかについて言うためにこれを持っています:
型 "cv voidへのポインタ"の右辺値は、オブジェクト型へのポインタに明示的に変換することができます。 objectへのポインタ型の値を「cv voidへのポインタ」に変換してから元のポインタ型に戻すと、元の値になります。
そのため、あなたのユースケースでは、標準化委員会があなたがstatic_cast
を使うことを意図していたことはかなり明白に思えます。
Reinterpret_castの用途の1つは、(IEEE 754)フロートにビット単位の演算を適用したい場合です。その一例が、Fast Inverse Square-Rootトリックです。
https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
浮動小数点数の2進表現を整数として扱い、それを右にシフトして定数から引き算し、それによって指数を半分にして否定します。 floatに変換した後、この近似をより正確にするために、Newton-Raphson反復を受けます。
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the deuce?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
これはもともとCで書かれていたので、Cキャストを使用しますが、類似のC++キャストはreinterpret_castです。
コンパイル時に継承をチェックするためにreinterprete_castを使うことができます。
ここを見てください: コンパイル時に継承をチェックするためにreinterpret_castを使う
まずintのような特定の型のデータがあります。
int x = 0x7fffffff://==nan in binary representation
次に、floatのような他の型と同じ変数にアクセスしたいとします。
float y = reinterpret_cast<float&>(x);
//this could only be used in cpp, looks like a function with template-parameters
または
float y = *(float*)&(x);
//this could be used in c and cpp
簡単:同じメモリが異なるタイプとして使用されていることを意味します。ですから、上記のようなint型としてのfloatのバイナリ表現をfloatに変換することができます。たとえば、0x80000000は-0です(仮数と指数はNULLですが、符号(msb)は1です。これは倍精度と倍精度の倍精度でも機能します。
最適化:多くのコンパイラでreinterpret_castが最適化されると思いますが、cキャスティングはポインタ演算によって行われます(値はメモリにコピーする必要があり、ポインタはcpuレジスタを指すことができません)。
注:どちらの場合も、キャストする前にキャスト値を変数に保存する必要があります。このマクロは役に立ちます。
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
template <class outType, class inType>
outType safe_cast(inType pointer)
{
void* temp = static_cast<void*>(pointer);
return static_cast<outType>(temp);
}
私はテンプレートを使用して簡単な安全なキャストを締めくくろうとしました。この解決法は関数へのポインタのキャストを保証しないことに注意してください。