Ik ben een beetje in de war met de toepasbaarheid van reinterpret_cast
versus static_cast
. Van wat ik heb gelezen, zijn de algemene regels om statische cast te gebruiken wanneer de typen kunnen worden geïnterpreteerd tijdens het compileren, vandaar het woord static
. Dit is de cast die de C++-compiler intern ook gebruikt voor impliciete casts.
reinterpret_cast
s zijn van toepassing in twee scenario’s:
- converteer integer-types naar pointer-types en vice versa
- converteer het ene aanwijzertype naar het andere. Het algemene idee dat ik krijg is dat dit niet overdraagbaar is en moet worden vermeden.
Waar ik een beetje in de war ben, is een gebruik dat ik nodig heb, ik bel C++ vanuit C en de C-code moet het C++-object vasthouden, dus in feite heeft het een void*
. Welke cast moet worden gebruikt om te converteren tussen de void *
en het Class-type?
Ik heb het gebruik van zowel static_cast
als reinterpret_cast
gezien? Hoewel van wat ik heb gelezen, lijkt het erop dat static
beter is omdat de cast tijdens het compileren kan plaatsvinden? Hoewel er staat dat je reinterpret_cast
moet gebruiken om van het ene aanwijzertype naar het andere te converteren?
Antwoord 1, autoriteit 100%
De C++-standaard garandeert het volgende:
static_cast
een aanwijzer van en naar void*
behoudt het adres. Dat wil zeggen dat a
, b
en c
allemaal naar hetzelfde adres verwijzen:
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
reinterpret_cast
garandeert alleen dat als je een aanwijzer naar een ander type cast, en dan reinterpret_cast
terug naar het oorspronkelijke type, je de oorspronkelijke waarde. Dus in het volgende:
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
a
en c
bevatten dezelfde waarde, maar de waarde van b
is niet gespecificeerd. (in de praktijk zal het meestal hetzelfde adres bevatten als a
en c
, maar dat is niet gespecificeerd in de standaard, en het is mogelijk niet waar op machines met complexere geheugensystemen .)
Voor casten van en naar void*
verdient static_cast
de voorkeur.
Antwoord 2, autoriteit 35%
Eén geval waarin reinterpret_cast
nodig is, is bij het gebruik van ondoorzichtige gegevenstypen. Dit komt vaak voor in leveranciers-API’s waarover de programmeur geen controle heeft. Hier is een gekunsteld voorbeeld waarbij een leverancier een API levert voor het opslaan en ophalen van willekeurige globale gegevens:
// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();
Om deze API te gebruiken, moet de programmeur zijn gegevens casten naar VendorGlobalUserData
en weer terug. static_cast
werkt niet, men moet reinterpret_cast
gebruiken:
// 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;
}
Hieronder staat een gekunstelde implementatie van de voorbeeld-API:
// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
Antwoord 3, autoriteit 28%
Het korte antwoord:
Als je niet weet waar reinterpret_cast
voor staat, gebruik het dan niet. Als je het in de toekomst nodig hebt, weet je het.
Volledig antwoord:
Laten we eens kijken naar de basisnummertypen.
Als u bijvoorbeeld int(12)
converteert naar unsigned float (12.0f)
, moet uw processor enkele berekeningen uitvoeren omdat beide getallen een verschillende bitweergave hebben. Dit is waar static_cast
voor staat.
Aan de andere kant, wanneer u reinterpret_cast
aanroept, roept de CPU geen berekeningen aan. Het behandelt gewoon een set bits in het geheugen alsof het een ander type had. Dus als je int*
converteert naar float*
met dit sleutelwoord, heeft de nieuwe waarde (na verwijzing van de aanwijzer) niets te maken met de oude waarde in wiskundige betekenis.
Voorbeeld:Het is waar dat reinterpret_cast
om één reden niet overdraagbaar is: bytevolgorde (endianness). Maar dit is vaak verrassend de beste reden om het te gebruiken. Laten we ons het voorbeeld voorstellen: u moet een binair 32-bits getal uit een bestand lezen en u weet dat het big endian is. Je code moet generiek zijn en goed werken op big endian (bijvoorbeeld sommige ARM) en little endian (bijvoorbeeld x86) systemen. Je moet dus de bytevolgorde controleren. Het is bekend tijdens het compileren, dus je kunt de functie Je kunt een functie schrijven om dit te bereiken:constexpr
schrijven:
/*constexpr*/ bool is_little_endian() {
std::uint16_t x=0x0001;
auto p = reinterpret_cast<std::uint8_t*>(&x);
return *p != 0;
}
Uitleg:de binaire weergave van x
in het geheugen kan 0000'0000'0000'0001
zijn ( groot) of 0000'0001'0000'0000
(little endian). Na herinterpretatie-casting kan de byte onder de p
-aanwijzer respectievelijk 0000'0000
of 0000'0001
zijn. Als u statische casting gebruikt, is dit altijd 0000'0001
, ongeacht welke endianness wordt gebruikt.
BEWERKEN:
In de eerste versie heb ik voorbeeldfunctie is_little_endian
gemaakt om constexpr
te zijn. Het compileert prima op de nieuwste gcc (8.3.0) maar de standaard zegt dat het illegaal is. De clang-compiler weigert het te compileren (wat correct is).
Antwoord 4, autoriteit 4%
De betekenis van reinterpret_cast
wordt niet gedefinieerd door de C++-standaard. Vandaar dat in theorie een reinterpret_cast
je programma zou kunnen laten crashen. In de praktijk proberen compilers te doen wat je verwacht, namelijk de stukjes die je doorgeeft interpreteren alsof ze het type zijn waarnaar je cast. Als je weet wat de compilers die je gaat gebruiken met reinterpret_cast
doen, kun je het gebruiken, maar om te zeggen dat het draagbaaris, zou liegen.
Voor het geval dat u beschrijft, en vrijwel elk geval waarin u reinterpret_cast
zou kunnen overwegen, kunt u in plaats daarvan static_cast
of een ander alternatief gebruiken. De standaard zegt onder meer dit over wat u van static_cast
kunt verwachten (§5.2.9):
Een r-waarde van het type “pointer naar cv void” kan expliciet worden geconverteerd naar een pointer naar objecttype. Een waarde van het type pointer naar object geconverteerd naar “pointer to cv void” en terug naar het originele pointertype zal zijn oorspronkelijke waarde hebben.
Dus voor jouw gebruik lijkt het vrij duidelijk dat de normalisatiecommissie van plan was dat je static_cast
zou gebruiken.
Antwoord 5, autoriteit 2%
Een gebruik van reinterpret_cast is als u bitsgewijze bewerkingen wilt toepassen op (IEEE 754) floats. Een voorbeeld hiervan was de Fast Inverse Square-Root-truc:
https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
Het behandelt de binaire representatie van de float als een geheel getal, verschuift het naar rechts en trekt het af van een constante, waardoor de exponent wordt gehalveerd en genegeerd. Nadat het weer is omgezet naar een vlotter, wordt het onderworpen aan een Newton-Raphson-iteratie om deze benadering nauwkeuriger te maken:
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;
}
Dit is oorspronkelijk geschreven in C, dus gebruikt C-casts, maar de analoge C++-cast is de reinterpret_cast.
Antwoord 6
U kunt reinterprete_cast gebruiken om de overerving te controleren tijdens het compileren.
Kijk hier:
Reinterpret_cast gebruiken om overerving te controleren tijdens het compileren
Antwoord 7
Hier is een variant van het programma van Avi Ginsburg die duidelijk de eigenschap illustreert van reinterpret_cast
genoemd door Chris Luengo, flodin en cmdLP: dat de compiler de aangewezen geheugenlocatie behandelt alsof het een object van het nieuwe type:
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
class A
{
public:
int i;
};
class B : public A
{
public:
virtual void f() {}
};
int main()
{
string s;
B b;
b.i = 0;
A* as = static_cast<A*>(&b);
A* ar = reinterpret_cast<A*>(&b);
B* c = reinterpret_cast<B*>(ar);
cout << "as->i = " << hex << setfill('0') << as->i << "\n";
cout << "ar->i = " << ar->i << "\n";
cout << "b.i = " << b.i << "\n";
cout << "c->i = " << c->i << "\n";
cout << "\n";
cout << "&(as->i) = " << &(as->i) << "\n";
cout << "&(ar->i) = " << &(ar->i) << "\n";
cout << "&(b.i) = " << &(b.i) << "\n";
cout << "&(c->i) = " << &(c->i) << "\n";
cout << "\n";
cout << "&b = " << &b << "\n";
cout << "as = " << as << "\n";
cout << "ar = " << ar << "\n";
cout << "c = " << c << "\n";
cout << "Press ENTER to exit.\n";
getline(cin,s);
}
Wat resulteert in uitvoer zoals deze:
as->i = 0
ar->i = 50ee64
b.i = 0
c->i = 0
&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i) = 00EFF978
&(c->i) = 00EFF978
&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c = 00EFF974
Press ENTER to exit.
Het is te zien dat het B-object eerst als B-specifieke gegevens in het geheugen wordt ingebouwd, gevolgd door het ingesloten A-object. De static_cast
retourneert correct het adres van het ingesloten A-object, en de aanwijzer gemaakt door static_cast
geeft correct de waarde van het gegevensveld. De aanwijzer die wordt gegenereerd door reinterpret_cast
behandelt de geheugenlocatie van b
alsof het een gewoon A-object is, en dus wanneer de aanwijzer het gegevensveld probeert te krijgen, retourneert hij een aantal B- specifieke gegevens alsof het de inhoud van dit veld is.
Eén gebruik van reinterpret_cast
is om een aanwijzer om te zetten in een geheel getal zonder teken (wanneer aanwijzers en gehele getallen zonder teken even groot zijn):
int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);
Antwoord 8
template <class outType, class inType>
outType safe_cast(inType pointer)
{
void* temp = static_cast<void*>(pointer);
return static_cast<outType>(temp);
}
Ik probeerde een conclusie te trekken en schreef een eenvoudige veilige cast met behulp van sjablonen.
Merk op dat deze oplossing niet garandeert dat er aanwijzers op een functie worden geplaatst.
Antwoord 9
Eerst heb je wat gegevens van een specifiek type, zoals int hier:
int x = 0x7fffffff://==nan in binary representation
Dan wil je toegang krijgen tot dezelfde variabele als een ander type zoals float:
U kunt kiezen tussen
float y = reinterpret_cast<float&>(x);
//this could only be used in cpp, looks like a function with template-parameters
of
float y = *(float*)&(x);
//this could be used in c and cpp
KORT: het betekent dat hetzelfde geheugen wordt gebruikt als een ander type. Dus je zou binaire weergaven van drijvers kunnen converteren als intype zoals hierboven om te drijven. 0x80000000 is -0 bijvoorbeeld (de mantissa en exponent zijn null, maar het teken, de MSB, is één. Dit werkt ook voor verdubbelingen en lange doubles.
Optimaliseren: ik denk dat Reinterpret_cast in veel compilers zou worden geoptimaliseerd, terwijl de C-casting wordt gemaakt door pointerarithmetica (de waarde moet naar het geheugen worden gekopieerd, kunnen aanwijzingen niet wijzen op CPU-registers).
Opmerking: in beide gevallen moet u de gegoten waarde in een variabele vóór cast opslaan! Deze macro kan helpen:
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
Antwoord 10
Snel antwoord: gebruik static_cast
als het compileert, anders resort naar reinterpret_cast
.
Antwoord 11
Lees de FAQ ! Holding C++ -gegevens in C kan riskant zijn.
In C++ kan een aanwijzer naar een object worden geconverteerd naar void *
zonder eventuele casts. Maar het is niet waar andersom. U hebt een static_cast
nodig om de originele aanwijzer terug te krijgen.