Static_cast, dynamic_cast, const_cast ve reinterpret_cast ne zaman kullanılmalıdır?


2493

Aşağıdakilerin uygun kullanımları nelerdir:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C tarzı döküm (type)value
  • İşlev tarzı döküm type(value)

Hangi özel durumlarda hangisinin kullanılacağına nasıl karar verilir?



3
Farklı türde dökümler kullanmanın bazı yararlı somut örnekleri için, bu diğer konudaki benzer bir sorunun ilk cevabını kontrol edebilirsiniz .
TeaMonkie

2
Yukarıdaki sorunuz için gerçekten iyi cevaplar bulabilirsiniz. Ama buraya bir nokta daha eklemek istiyorum, @ e.James "Bu yeni c ++ döküm operatörlerinin yapabileceği ve c stili döküm yapamayacağı hiçbir şey yoktur. Bunlar daha iyi kod okunabilirliği için az çok eklenir."
BreakBadSP

@BreakBadSP Yeni yayınlar yalnızca daha iyi kod okunabilirliği için değildir . Değerleri yerine sabitleri veya işaretçileri dökmek gibi tehlikeli şeyler yapmayı zorlaştırmak için oradalar. static_cast, ac tarzı oyunculardan daha tehlikeli bir şey yapmak için çok daha az olasılık sunar!
FourtyTwo

@FourtyTwo kabul etti
BreakBadSP

Yanıtlar:


2570

static_castkullanmaya çalışmanız gereken ilk oyuncu. Bu örtük (örneğin türleri arasındaki dönüşümler gibi şeyler yapar intiçin floatveya işaretçi void*) ve aynı zamanda açık dönüştürme işlevleri (veya örtülü olanlar) çağırabilir. Çoğu durumda, açıkça belirtmek static_castgerekli değildir, ancak T(something)sözdiziminin eşdeğer olduğunu (T)somethingve bundan kaçınılması gerektiğini belirtmek önemlidir (daha sonra daha fazla). T(something, something_else)Ancak A güvenlidir ve kurucuyu çağırması garanti edilir.

static_castkalıtım hiyerarşileri yoluyla da kullanılabilir. Yukarı doğru (bir temel sınıfa doğru) döküm yaparken gereksizdir, ancak aşağı doğru döküm yaparken, virtualkalıtımdan geçmediği sürece kullanılabilir . Ancak denetleme yapmaz ve static_castbir hiyerarşiyi aslında nesnenin türü olmayan bir türe indirgemek tanımsız bir davranıştır .


const_castconstbir değişkeni kaldırmak veya eklemek için kullanılabilir ; başka hiçbir C ++ dökümü onu kaldıramaz (hatta değil reinterpret_cast). Önceden bir constdeğerin değiştirilmesinin yalnızca orijinal değişken ise tanımsız olduğunu belirtmek önemlidir const; constbildirilmemiş bir şeye referans almak için kullanırsanız const, güvenlidir. Bu, örneğin üye fonksiyonlarını aşırı yüklerken faydalı olabilir const. constBir üye fonksiyonunun aşırı yüklenmesi gibi bir nesneye eklemek için de kullanılabilir .

const_castvolatiledaha az yaygın olsa da benzer şekilde çalışır .


dynamic_castsadece polimorfizmi işlemek için kullanılır. Bir işaretçiyi veya herhangi bir polimorfik türe referansı başka herhangi bir sınıf türüne atayabilirsiniz (polimorfik bir türün bildirilen veya devralınan en az bir sanal işlevi vardır). Sadece aşağı doğru dökmekten daha fazlası için kullanabilirsiniz - yanlara veya hatta başka bir zincire kadar dökebilirsiniz. dynamic_castİstenen nesneyi ararlar ve mümkünse bunu dönecektir. Eğer yapamazsa, nullptrbir işaretçi durumunda geri dönecek ya da std::bad_castbaşvuru durumunda atılacaktır .

dynamic_castolsa da, bazı sınırlamaları vardır. Kalıtım hiyerarşisinde ('korkunç elmas' olarak adlandırılan) aynı türde birden fazla nesne varsa ve miras kullanmıyorsanız işe yaramaz virtual. Ayrıca sadece kamusal mirastan geçebilir - her zaman seyahat etmek protectedveya privatemiras almak başarısız olur . Bununla birlikte, bu tür bir kalıtım biçimi nadir olduğu için bu nadiren bir konudur.


reinterpret_casten tehlikeli dökümdür ve çok az kullanılmalıdır. Bir türü doğrudan diğerine dönüştürür - örneğin değeri bir işaretçiden diğerine çevirmek veya bir işaretçiyi bir intveya başka türlü kötü şeylerde saklamak gibi . Büyük ölçüde, sadece birlikte olsun garanti reinterpret_castorijinal türüne sonucu geri dökme normalde bile, tam olarak aynı değere alacak (ancak değil orta tip orijinal tip küçükse). Yapamayan birkaç dönüşüm reinterpret_castde var. Öncelikle, ham veri akışını gerçek verilere dönüştürmek veya hizalanmış verilere işaretçinin düşük bitlerinde veri depolamak gibi özellikle garip dönüşümler ve bit manipülasyonları için kullanılır.


C stili döküm ve işlev stili döküm , sırasıyla (type)objectveya kullanan dökümlerdir type(object)ve işlevsel olarak eşdeğerdir. Bunlar, aşağıdakilerden başarılı olan aşağıdakilerden birincisi olarak tanımlanır:

  • const_cast
  • static_cast (erişim kısıtlamalarına uymamakla birlikte)
  • static_cast (yukarıya bakın), sonra const_cast
  • reinterpret_cast
  • reinterpret_cast, sonra const_cast

Bu nedenle, bazı durumlarda diğer dökümlerin yerine kullanılabilir, ancak a'ya dönüşme yeteneği nedeniyle son derece tehlikeli olabilir reinterpret_castve ikincisi, açık döküm gerektiğinde, static_castbaşarılı reinterpret_castolacağınızdan veya başarısız olacağınızdan emin olmadan tercih edilmelidir. . O zaman bile, daha uzun, daha açık seçeneği düşünün.

C tarzı dökümler static_cast, a yaparken erişim kontrolünü de yok sayar , bu da başka hiçbir dökümün yapamayacağı bir işlem gerçekleştirme yeteneğine sahip oldukları anlamına gelir. Bu çoğunlukla bir çamurdur ve bence C tarzı dökümlerden kaçınmanın başka bir nedeni.


17
dynamic_cast sadece polimorfik tipler içindir. yalnızca türetilmiş bir sınıfa yayın yaparken kullanmanız gerekir. static_cast işlevine özellikle ihtiyacınız olmadığı sürece static_cast kesinlikle ilk seçenektir. Genel olarak mucizevi bir gümüş mermi "tip kontrol döküm" değil.
jalf

2
Mükemmel cevap! Hızlı bir açıklama: static_cast, Türetilmiş * olması durumunda hiyerarşiyi yayınlamak ve Base * & 'ya yayınlamak için gerekli olabilir, çünkü çift işaretçiler / referanslar otomatik olarak hiyerarşiyi oluşturmaz. İki dakika önce böyle (açıkçası, yaygın olmayan) bir durumla karşılaştım. ;-)
bartgol

5
* "Başka hiçbir C ++ cast const(hatta değil reinterpret_cast) kaldıramaz " ... gerçekten? Ne olmuş reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686

29
Yukarıda eksik önemli bir ayrıntı, static_cast statik veya reinterpret_cast ile karşılaştırıldığında bir çalışma zamanı performans cezası olduğunu düşünüyorum. Bu, örneğin gerçek zamanlı yazılımda önemlidir.
jfritz42

5
reinterpret_castBir API'nin opak veri türleri kümesi ile uğraşırken bunun genellikle tercih edilen silah olduğunu belirtmek gerekebilir
Class Skeleton

333

dynamic_castKalıtım hiyerarşisinde işaretçileri / referansları dönüştürmek için kullanın .

static_castNormal tür dönüşümler için kullanın .

reinterpret_castBit örüntülerinin düşük seviye yeniden yorumlanması için kullanın . Çok dikkatli kullanın.

const_castDöküm yapmak için kullanın const/volatile. Bir const-false API kullanarak sıkışıp kalmadıkça bunu önlemek.


2
Dynamic_cast ile dikkatli olun. RTTI'ye dayanır ve bu paylaşılan kütüphane sınırları arasında beklendiği gibi çalışmaz. Sadece bağımsız olarak yürütülebilir ve paylaşılan kitaplık inşa çünkü farklı yapıları arasında RTTI senkronize etmek için standart bir yolu yoktur. Bu nedenle Qt kütüphanesinde tipleri kontrol etmek için QObject tipi bilgisini kullanan qobject_cast <> vardır.
user3150128

198

(Yukarıda birçok teorik ve kavramsal açıklama verilmiştir)

Aşağıda static_cast , dynamic_cast , const_cast , reinterpret_cast kullandığım bazı pratik örnekler var .

(Ayrıca açıklamayı anlamak için bunu ifade eder: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

dynamic_cast:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}

31
Diğer cevapların bazılarının teorisi iyidir, ama yine de kafa karıştırıcıdır, diğer cevapları okuduktan sonra bu örnekleri görmek gerçekten anlamlı olur. Bu örnekler olmadan, hala emin değildim, ancak onlarla birlikte, diğer cevapların ne anlama geldiğinden eminim.
Solx

1
Reinterpret_cast'in son kullanımı hakkında: bu kullanmakla aynı şey değil static_cast<char*>(&val)mi?
Lorenzo Belli

3
@LorenzoBelli Tabii ki hayır. Onu denedin mi? İkincisi geçerli C ++ değildir ve derlemeyi engeller. static_castyalnızca tanımlanmış dönüşümleri olan türler arasında, kalıtım yoluyla veya / ile görünür ilişki arasında çalışır void *. Diğer her şey için başka oyuncular var. reinterpret castherhangi bir char *türün herhangi bir nesnenin temsilinin okunmasına izin vermesine izin verilir - ve bu anahtar kelimenin yararlı olduğu tek durumlardan biri, uygulama / tanımsız davranışların yaygın bir üreticisi değil. Ancak bu 'normal' bir dönüşüm olarak kabul edilmez, bu nedenle (genellikle) çok muhafazakarlar tarafından izin verilmez static_cast.
underscore_d

2
reinterpret_cast, veritabanları gibi sistem yazılımlarıyla çalışırken oldukça yaygındır. Çoğu durumda, sayfada saklanan veri türünün ne olduğu hakkında hiçbir fikri olmayan kendi sayfa yöneticinizi yazarsınız ve yalnızca geçersiz bir işaretçi döndürür. Bu bir döküm yeniden yorumlamak ve istediği gibi çıkarım yapmak için yüksek seviyelere kadar.
Sohaib

1
Const_cast örneği Tanımsız Davranış gösterir. Sabit olarak bildirilen bir değişkenin derlemesi kaldırılamaz. Bununla birlikte, const referansı alan bir fonksiyona geçirilen const olmayan olarak bildirilen bir değişken, bu fonksiyonda UB olduğu için daraltılabilir.
Johann Gerell

99

Biraz içsel olanı biliyorsanız yardımcı olabilir ...

static_cast

  • C ++ derleyicisi zaten float gibi ölçekleyici türleri arasında int dönüştürmek için nasıl bilir. static_castOnlar için kullanın .
  • Derleyiciden türden dönüştürmesini Aistediğinizde B, static_castçağrının Byapıcısını Aparam olarak iletir. Alternatif olarak, Abir dönüştürme operatörüne (yani A::operator B()) sahip olabilir. Eğer Bböyle yapıcı yok, ya Abir dönüşüm operatörü yoktur, o zaman derleme zamanı hatası alırsınız.
  • Cast A*için B*A ve B kalıtım hiyerarşisi (veya boşluk) ise her zaman aksi takdirde derleme hatası alıyorum başarır.
  • Yakaladım : Eğer türetilmiş işaretçisi taban işaretçisi döküm ancak gerçek nesne gerçekten türünü türetilmiş değilse o zaman Eğer yok hatası alıyorum. Kötü işaretçi ve büyük olasılıkla çalışma zamanında bir segfault alırsınız. Aynı şey için A&de geçerli B&.
  • Gotcha : Derived'den Base'e veya viceversa'ya yeni bir kopya oluştur ! C # / Java'dan gelen insanlar için bu büyük bir sürpriz olabilir, çünkü sonuç temel olarak Derived'den oluşturulan kesilmiş bir nesnedir.

dynamic_cast

  • dynamic_cast, yayınlamanın geçerli olup olmadığını anlamak için çalışma zamanı türü bilgilerini kullanır. Örneğin, (Base*)için (Derived*)işaretçi aslında türetilmiş bir tür değilse başarısız olabilir.
  • Bu, dynamic_cast'in static_cast'e göre çok pahalı olduğu anlamına gelir!
  • İçin A*için B*döküm ise, geçersiz sonra dynamic_cast nullptr dönecektir.
  • İçin A&için B&dökme geçersizse sonra dynamic_cast bad_cast özel durum oluşturur.
  • Diğer oyunculardan farklı olarak, çalışma zamanı yükü vardır.

const_cast

  • Static_cast, const için const olmayan şeyler yapabilirken, başka bir yoldan gidemez. Const_cast her iki yolu da yapabilir.
  • Bunun kullanışlı olduğu bir örnek set<T>, anahtarını değiştirmediğinizden emin olmak için öğelerini yalnızca const olarak döndüren bir kapsayıcıdan yinelenmektir . Ancak amacınız nesnenin anahtar olmayan üyelerini değiştirmekse, o zaman sorun olmamalıdır. Sabitliği kaldırmak için const_cast kullanabilirsiniz.
  • Uygulamak istediğiniz başka bir örnektir T& SomeClass::foo()yanı sıra const T& SomeClass::foo() const. Kod çoğaltmasını önlemek için, bir işlevin değerini diğerinden döndürmek için const_cast uygulayabilirsiniz.

reinterpret_cast

  • Bu temel olarak, bu baytları bu hafıza konumuna aldığını ve verilen nesne olarak düşündüğünü söylüyor.
  • Örneğin, şamandıradaki bitlerin nasıl göründüğünü görmek için 4 bayt float'ı 4 bayt int'e yükleyebilirsiniz.
  • Açıkçası, veriler tür için doğru değilse segfault alabilirsiniz.
  • Bu yayın için çalışma zamanı yükü yok.

Dönüşüm operatörü bilgilerini ekledim, ancak düzeltilmesi gereken birkaç şey daha var ve bunu çok rahat güncellemiyorum. Öğeler: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Eğer şanslıysanız çalışma zamanında bir segfault neden olabilir UB olsun. 2. Dinamik dökümler çapraz dökümde de kullanılabilir. 3. Sabit oyuncular bazı durumlarda UB ile sonuçlanabilir. Kullanımı mutablemantıksal sabitliği uygulamak için daha iyi bir seçim olabilir.
Adrian

1
@Adrian, her açıdan haklısın. Cevap az ya da çok başlangıç ​​seviyesindeki insanlar için yazılmıştır ve onları, diğer tüm komplikasyonlarla mutable, çapraz döküm
vb.İle

16

Does bu sorunuza cevap?

Hiç kullanmadım reinterpret_castve ihtiyaç duyan bir davaya girmenin kötü bir tasarım kokusu olmadığını merak ediyorum. Üzerinde çalıştığım kod tabanında dynamic_castçok kullanılıyor. Aradaki fark static_cast, istediğiniz dynamic_cast(daha güvenli) veya daha fazla (daha fazla yük) olabilecek bir çalışma zamanı denetiminin yapılmasıdır ( msdn'ye bakın ).


3
Ben tek bir amaç için reintrepret_cast kullandım - bit bir çift (platformumda uzun uzun aynı boyutta) elde.
Joshua

2
reinterpret_cast, örneğin COM nesneleriyle çalışmak için gereklidir. CoCreateInstance (), void ** (son parametre) türünde bir çıkış parametresine sahiptir; burada işaretçinizi örneğin "INetFwPolicy2 * pNetFwPolicy2" olarak bildirilir. Bunu yapmak için reinterpret_cast <void **> (& pNetFwPolicy2) gibi bir şey yazmanız gerekir.
Serge Rogatch

1
Belki farklı bir yaklaşım var, ama ben reinterpret_castbir dizi veri parçaları ayıklamak için kullanın . Örneğin char*, hareket etmem ve farklı türlerde bireysel ilkelleri almam gereken paketlenmiş ikili verilerle dolu büyük bir arabelleğim varsa . Böyle bir şey:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
James Matta

Daha önce hiç kullanmadım reinterpret_cast, bunun için çok fazla kullanım yok.
Balinaların Büyücüsü Pika

Şahsen şimdiye kadar sadece reinterpret_castbir sebepten dolayı kullanıldığını gördüm . Ben bir veritabanında bir "blob" veri tipinde saklanan ham nesne verileri gördüm, sonra veri veritabanından alındığında, reinterpret_castbu ham verileri nesneye dönüştürmek için kullanılır.
ImaginaryHuman072889

15

Şimdiye kadar olan diğer cevaplara ek olarak, burada static_castyeterli olmadığı reinterpret_castve ihtiyaç duyulmadığı açık olmayan bir örnek . Bir çıkış parametresinde farklı sınıftaki nesnelere (ortak bir temel sınıfı paylaşmayan) işaretçiler döndüren bir işlev olduğunu varsayalım. Bu işlevin gerçek bir örneği ( CoCreateInstance()aslında son parametreye bakın void**). Bu işlevden belirli bir nesne sınıfı istediğinizi varsayalım, böylece işaretçinin türünü önceden bilirsiniz (genellikle COM nesneleri için yaparsınız). Bu durumda işaretçiyi işaretçinize void**şununla atamazsınız static_cast: ihtiyacınız var reinterpret_cast<void**>(&yourPointer).

Kodda:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

Bununla birlikte, static_castbasit işaretçiler (işaretleyicilere işaretçiler için değil) için çalışır, bu nedenle yukarıdaki kod reinterpret_cast(ekstra değişkenin bir fiyatında) aşağıdaki şekilde önlemek için yeniden yazılabilir :

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

&static_cast<void*>(pNetFwPolicy2)Bunun yerine böyle bir şey olmaz static_cast<void**>(&pNetFwPolicy2)mı?
jp48

9

Diğer cevaplar güzel C ++ atmalarını arasındaki tüm farklılıkları tarif ederken, ben C tarzı yayınları kullanmamalısınız neden kısa bir not eklemek istiyorum (Type) varve Type(var).

C ++ yeni başlayanlar için C tarzı dökümler, C ++ yayınları (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) üzerinde üst küme işlemi gibi görünür ve birisi bunları C ++ yayınları üzerinde tercih edebilir . Aslında C tarzı döküm süper set ve yazmak için daha kısa.

C tarzı dökümlerin temel sorunu geliştiricinin oyuncu kadrosunun gerçek niyetini gizlemesidir. C stili dökümler, static_cast <> () ve dynamic_cast <> () tarafından yapılan normalde güvenli dökümlerden const değişkeninin kaldırılabileceği const_cast <> () gibi potansiyel olarak tehlikeli dökümlere kadar neredeyse tüm döküm türlerini yapabilir değiştirilebilir ve tamsayı değerlerini işaretçilerle yeniden yorumlayabilen reinterpret_cast <> ().

İşte örnek.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

C ++ dillerinin dile eklenmesinin ana nedeni, bir geliştiricinin niyetlerini netleştirmesine izin vermekti - neden bu kadroyu yapacak. C ++ 'da mükemmel şekilde geçerli olan C tarzı dökümler kullanarak, kodunuzu daha az okunabilir hale getirir ve özellikle kodunuzu oluşturmayan diğer geliştiriciler için daha fazla hataya açık hale gelirsiniz. Bu nedenle, kodunuzu daha okunabilir ve açık hale getirmek için her zaman C stili yayınlar yerine C ++ yayınlarını tercih etmelisiniz.

İşte Bjarne Stroustrup'un (C ++ yazarı) kitabı C ++ Programlama Dili 4. baskıdan kısa bir alıntı - sayfa 302.

Bu C tarzı döküm, adlandırılan dönüşüm operatörlerinden çok daha tehlikelidir, çünkü gösterimi büyük bir programda tespit etmek daha zordur ve programcı tarafından amaçlanan dönüşüm türü açık değildir.


5

Anlamak için aşağıdaki kod snippet'ini ele alalım:

struct Foo{};
struct Bar{};

int main(int argc, char** argv)
{
    Foo* f = new Foo;

    Bar* b1 = f;                              // (1)
    Bar* b2 = static_cast<Bar*>(f);           // (2)
    Bar* b3 = dynamic_cast<Bar*>(f);          // (3)
    Bar* b4 = reinterpret_cast<Bar*>(f);      // (4)
    Bar* b5 = const_cast<Bar*>(f);            // (5)

    return 0;
}

Yalnızca satır (4) hatasız derlenir. Yalnızca reinterpret_cast , bir işaretçiyi bir nesneye işaretçiyle ilgisiz herhangi bir nesne türüne dönüştürmek için kullanılabilir.

Dikkat edilmesi gereken noktalardan biri şudur: dynamic_cast çalışma zamanında başarısız olur, ancak çoğu derleyicide de derleme başarısız olur çünkü imlecin yapısında sanal işlevler yoktur, yani dynamic_cast sadece polimorfik sınıf işaretçileriyle çalışır .

C ++ cast ne zaman kullanılır :

  • Kullanım static_cast değer dönüşümü yapmaz ya da biz açıkça gerektiğinde onun üst sınıfa sınıftan bir işaretçi yukarı döküm C tarzı döküm eşdeğer olarak.
  • Const_cast kullanınSabit niteleyiciyi kaldırmak için .
  • Tamsayı ve diğer işaretçi türlerine veya işaretçi türlerinden güvenli olmayan işaretçi dönüşümleri yapmak için reinterpret_cast kullanın . Bunu yalnızca ne yaptığımızı biliyorsak ve takma adlandırma sorunlarını anlarsak kullanın.

2

static_castvs dynamic_castvs reinterpret_castinternals downcast / upcast görünüm

Bu cevapta, somut bir inişli çıkışlı / inişli çıkış örneğinde bu üç mekanizmayı karşılaştırmak ve nasıl karşılaştırıldığına dair somut bir anlayış sağlamak için altta yatan işaretlere / bellek / düzeneğe ne olduğunu analiz etmek istiyorum.

Bunun, bu yayınların nasıl farklı olduğu konusunda iyi bir sezgi vereceğine inanıyorum:

  • static_cast: çalışma zamanında bir adres ofseti yapar (düşük çalışma zamanı etkisi) ve bir downcast'in doğru olduğunu kontrol etmez.

  • dyanamic_cast: çalışma zamanında aynı adres ofsetini yapar, aynı static_castzamanda ve RTTI kullanarak bir downcast'in doğru olup olmadığını pahalı bir güvenlik kontrolü yapar.

    Bu güvenlik denetimi, bir temel sınıf işaretçisinin çalışma zamanında belirli bir türde olup nullptrolmadığını, geri dönüşünü geçersiz bir downcast işaret ettiğini kontrol ederek sorgulamanızı sağlar .

    Bu nedenle, kodunuz bunu kontrol edemiyor nullptrve geçerli bir iptal edilmedi eylemi gerçekleştiremiyorsa, yalnızca static_castdinamik döküm yerine kullanmanız gerekir .

    Bir iptal işlemi kodunuzun gerçekleştirebileceği tek işlemse, belki de yalnızca dynamic_casthata ayıklama yapılarını ( -NDEBUG) etkinleştirmek ve hızlı çalışmalarınızı yavaşlatmamak için static_castörneğin burada yapıldığı gibi başka şekilde kullanmak istersiniz .

  • reinterpret_cast: çalışma zamanında hiçbir şey yapmaz, adres ofseti bile. İşaretçi, temel sınıfın bile çalışmadığından tam olarak doğru türün üzerine gelmelidir. Ham bayt akışları sürece genellikle bunu istemezsiniz.

Aşağıdaki kod örneğini düşünün:

main.cpp

#include <iostream>

struct B1 {
    B1(int int_in_b1) : int_in_b1(int_in_b1) {}
    virtual ~B1() {}
    void f0() {}
    virtual int f1() { return 1; }
    int int_in_b1;
};

struct B2 {
    B2(int int_in_b2) : int_in_b2(int_in_b2) {}
    virtual ~B2() {}
    virtual int f2() { return 2; }
    int int_in_b2;
};

struct D : public B1, public B2 {
    D(int int_in_b1, int int_in_b2, int int_in_d)
        : B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
    void d() {}
    int f2() { return 3; }
    int int_in_d;
};

int main() {
    B2 *b2s[2];
    B2 b2{11};
    D *dp;
    D d{1, 2, 3};

    // The memory layout must support the virtual method call use case.
    b2s[0] = &b2;
    // An upcast is an implicit static_cast<>().
    b2s[1] = &d;
    std::cout << "&d           " << &d           << std::endl;
    std::cout << "b2s[0]       " << b2s[0]       << std::endl;
    std::cout << "b2s[1]       " << b2s[1]       << std::endl;
    std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
    std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;

    // Now for some downcasts.

    // Cannot be done implicitly
    // error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
    // dp = (b2s[0]);

    // Undefined behaviour to an unrelated memory address because this is a B2, not D.
    dp = static_cast<D*>(b2s[0]);
    std::cout << "static_cast<D*>(b2s[0])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[0])->int_in_d  " << dp->int_in_d << std::endl;

    // OK
    dp = static_cast<D*>(b2s[1]);
    std::cout << "static_cast<D*>(b2s[1])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[1])->int_in_d  " << dp->int_in_d << std::endl;

    // Segfault because dp is nullptr.
    dp = dynamic_cast<D*>(b2s[0]);
    std::cout << "dynamic_cast<D*>(b2s[0])           " << dp           << std::endl;
    //std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;

    // OK
    dp = dynamic_cast<D*>(b2s[1]);
    std::cout << "dynamic_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;

    // Undefined behaviour to an unrelated memory address because this
    // did not calculate the offset to get from B2* to D*.
    dp = reinterpret_cast<D*>(b2s[1]);
    std::cout << "reinterpret_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}

Aşağıdakilerle derleyin, çalıştırın ve sökün:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out

nerede çalıştırmayı karşılaştırmayı kolaylaştırmak için ASLR'yi devre dışı bırakmaksetarch için kullanılır .

Olası çıktı:

&d           0x7fffffffc930
b2s[0]       0x7fffffffc920
b2s[1]       0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0])            0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d  1
static_cast<D*>(b2s[1])            0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d  3
dynamic_cast<D*>(b2s[0])           0
dynamic_cast<D*>(b2s[1])           0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1])           0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767

Şimdi, sanal yöntem çağrılarını verimli bir şekilde desteklemek için https://en.wikipedia.org/wiki/Virtual_method_table adresinde belirtildiği gibi , bellek veri yapısı aşağıdaki Dgibi görünmelidir:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

D:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

Anahtar gerçek şu ki, içerisindeki bellek veri yapısı D, içerideki B1ve B2dahili olanla uyumlu olan bellek yapısını içerir .

Bu nedenle kritik sonuca ulaşıyoruz:

upcast veya downcast yalnızca işaretçi değerini derleme zamanında bilinen bir değere kaydırmalıdır.

Bu şekilde, Dtemel tür dizisine geçtiğinde, cast türü aslında bu dengeyi hesaplar ve tam olarak B2bellekte geçerli gibi görünen bir şeyi işaret eder :

b2s[1] = &d;

Dbunun yerine bunun için vtable olması B2ve bu nedenle tüm sanal çağrılar şeffaf bir şekilde çalışır.

Şimdi nihayet tip dökümüne ve somut örneğimizin analizine geri dönebiliriz.

Stdout çıktısından şunu görüyoruz:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Bu nedenle, static_castorada yapılan örtük D, 0x7fffffffc930'daki tam veri yapısından 0x7fffffffc940'takine B2benzer bir şekilde ofseti doğru bir şekilde hesapladı . Ayrıca, 0x7fffffffc930 ve 0x7fffffffc940 arasında yatan şeyin muhtemelen B1veri ve vtable olduğunu da çıkarıyoruz.

Sonra, downcast bölümlerinde, geçersiz olanların nasıl başarısız olduğunu ve nedenini anlamak artık kolay:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: derleyici sadece denemek ve B2içeren bir denemek için derleme zamanı bayt 0x10 gittiD

    Ancak b2s[0]bir a olmadığı için D, şimdi tanımlanmamış bir hafıza bölgesine işaret ediyor.

    Sökme:

    49          dp = static_cast<D*>(b2s[0]);
       0x0000000000000fc8 <+414>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fcc <+418>:   48 85 c0        test   %rax,%rax
       0x0000000000000fcf <+421>:   74 0a   je     0xfdb <main()+433>
       0x0000000000000fd1 <+423>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fd5 <+427>:   48 83 e8 10     sub    $0x10,%rax
       0x0000000000000fd9 <+431>:   eb 05   jmp    0xfe0 <main()+438>
       0x0000000000000fdb <+433>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000000fe0 <+438>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    yani GCC'nin şunları yaptığını görüyoruz:

    • işaretçinin NULL olup olmadığını kontrol edin ve evet ise NULL döndürün
    • Aksi ulaşması ondan 0x10 çıkarma Dyok ki
  • dynamic_cast<D*>(b2s[0]) 0: C ++ aslında kadronun geçersiz olduğunu ve geri döndüğünü buldu nullptr!

    Bunun derleme zamanında yapılmasının bir yolu yoktur ve bunu sökme işleminden doğrulayacağız:

    59          dp = dynamic_cast<D*>(b2s[0]);
       0x00000000000010ec <+706>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x00000000000010f0 <+710>:   48 85 c0        test   %rax,%rax
       0x00000000000010f3 <+713>:   74 1d   je     0x1112 <main()+744>
       0x00000000000010f5 <+715>:   b9 10 00 00 00  mov    $0x10,%ecx
       0x00000000000010fa <+720>:   48 8d 15 f7 0b 20 00    lea    0x200bf7(%rip),%rdx        # 0x201cf8 <_ZTI1D>
       0x0000000000001101 <+727>:   48 8d 35 28 0c 20 00    lea    0x200c28(%rip),%rsi        # 0x201d30 <_ZTI2B2>
       0x0000000000001108 <+734>:   48 89 c7        mov    %rax,%rdi
       0x000000000000110b <+737>:   e8 c0 fb ff ff  callq  0xcd0 <__dynamic_cast@plt>
       0x0000000000001110 <+742>:   eb 05   jmp    0x1117 <main()+749>
       0x0000000000001112 <+744>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000001117 <+749>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    İlk olarak bir NULL kontrolü vardır ve çıkış NULL ise NULL döndürür.

    Aksi takdirde, RDX, RSI ve RDI ve çağrılarda bazı argümanlar kurar __dynamic_cast.

    Bunu daha fazla analiz etmek için sabrım yok, ancak diğerlerinin söylediği gibi, bunun çalışmasının tek yolu __dynamic_cast, sınıf hiyerarşisini temsil eden bazı ekstra RTTI bellek içi veri yapılarına erişmektir.

    Bu nedenle B2, bu tablonun girişinden başlamalı , sonra bu tür hiyerarşisinde bir tür Dtahmini için vtable'ı bulana kadar yürümelidir b2s[0].

    Bu yüzden dökümün yeniden yorumlanması potansiyel olarak pahalıdır! İşte bir dönüştürücü bir astar yama örnek dynamic_casta static_castkarmaşık bir projede% 33 çalışma zamanı düşük! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940bu sadece bize körü körüne inanıyor: bir Dadres olduğunu söyledik b2s[1]ve derleyici ofset hesaplama yapmıyor.

    Ancak bu yanlıştır, çünkü D aslında 0x7fffffffc930'dadır, 0x7fffffffc940'ta olan şey D içindeki B2 benzeri yapıdır! Böylece çöp kutusuna erişilir.

    Bunu -O0, değeri sadece hareket ettiren korkunç montajdan doğrulayabiliriz:

    70          dp = reinterpret_cast<D*>(b2s[1]);
       0x00000000000011fa <+976>:   48 8b 45 d8     mov    -0x28(%rbp),%rax
       0x00000000000011fe <+980>:   48 89 45 98     mov    %rax,-0x68(%rbp)

İlgili sorular:

Ubuntu 18.04 amd64, GCC 7.4.0 üzerinde test edilmiştir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.