เมื่อใดควรใช้ static_cast, dynamic_cast, const_cast และ reinterpret_cast


2492

อะไรคือการใช้งานที่เหมาะสมของ:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • หล่อสไตล์ C (type)value
  • ฟังก์ชั่นหล่อสไตล์ type(value)

เราจะตัดสินใจได้อย่างไรว่าจะใช้ในกรณีใด



3
สำหรับตัวอย่างที่เป็นรูปธรรมที่มีประโยชน์บางชนิดของการใช้ที่แตกต่างกันของการปลดเปลื้องคุณสามารถตรวจสอบคำตอบแรกในคำถามที่คล้ายกันในหัวข้ออื่น
TeaMonkie

2
คุณสามารถหาคำตอบที่ดีสำหรับคำถามของคุณด้านบน แต่ฉันอยากจะเพิ่มอีกจุดหนึ่งที่นี่ @ e.James "ไม่มีอะไรที่ c ++ cast ตัวใหม่เหล่านี้สามารถทำได้และ c style cast ไม่สามารถเพิ่มได้มากขึ้นหรือน้อยลงเพื่อให้อ่านรหัสได้ดีขึ้น"
BreakBadSP

@BreakBadSP casts ใหม่ไม่เพียง แต่จะสามารถอ่านโค้ดได้ดีขึ้น พวกเขาอยู่ที่นั่นเพื่อทำให้มันยากขึ้นในการทำสิ่งที่เป็นอันตรายเช่นการกำจัด const หรือการชี้พอยเตอร์แทนค่าของพวกเขา static_cast มีความเป็นไปได้ที่จะทำสิ่งที่อันตรายน้อยกว่านักแสดงสไตล์ ac!
FourtyTwo

@ FourtyTwo ตกลง
BreakBadSP

คำตอบ:


2570

static_castเป็นนักแสดงคนแรกที่คุณควรพยายามใช้ มันทำสิ่งต่าง ๆ เช่นการแปลงโดยนัยระหว่างประเภท (เช่นintถึงfloatหรือตัวชี้ไปยังvoid*) และยังสามารถเรียกใช้ฟังก์ชันการแปลงที่ชัดเจน (หรือที่อยู่โดยนัย) ในหลายกรณีการระบุอย่างชัดเจนstatic_castไม่จำเป็น แต่เป็นสิ่งสำคัญที่จะต้องทราบว่าT(something)ไวยากรณ์นั้นเทียบเท่า(T)somethingและควรหลีกเลี่ยง (เพิ่มเติมในภายหลัง) อย่างไรก็ตามT(something, something_else)มีความปลอดภัยและรับประกันว่าจะเรียกตัวสร้าง

static_castยังสามารถส่งผ่านลำดับชั้นการสืบทอด ไม่จำเป็นเมื่อทำการร่ายขึ้นไป (ไปยังคลาสเบส) แต่เมื่อทำการร่ายลงมันสามารถใช้งานได้ตราบใดที่มันไม่ผ่านการvirtualถ่ายทอดทางพันธุกรรม มันไม่ได้ทำการตรวจสอบและมันเป็นพฤติกรรมที่ไม่ได้กำหนดเพื่อstatic_castลดลำดับชั้นเป็นประเภทที่ไม่ใช่ประเภทของวัตถุ


const_castสามารถใช้เพื่อลบหรือเพิ่มconstตัวแปร นักแสดง C ++ รายอื่นไม่สามารถลบได้ (ไม่ใช่แม้แต่reinterpret_cast) เป็นสิ่งสำคัญที่จะต้องทราบว่าการแก้ไขconstค่าเดิมนั้นไม่ได้กำหนดไว้หากตัวแปรดั้งเดิมคือconst; หากคุณใช้เพื่อconstถอดการอ้างอิงถึงสิ่งที่ไม่ได้ประกาศconstไว้มันปลอดภัย สิ่งนี้มีประโยชน์เมื่อโอเวอร์โหลดฟังก์ชันของสมาชิกตามconstเช่น นอกจากนี้ยังสามารถใช้เพื่อเพิ่มconstวัตถุเช่นการเรียกฟังก์ชั่นสมาชิกเกินพิกัด

const_castยังใช้งานได้เหมือนกันvolatileแม้ว่าจะเป็นเรื่องธรรมดาน้อยกว่า


dynamic_castใช้เฉพาะสำหรับการจัดการความแตกต่าง คุณสามารถใช้พอยน์เตอร์หรือการอ้างอิงถึงชนิด polymorphic ใด ๆ ไปยังประเภทคลาสอื่น ๆ (ประเภท polymorphic มีอย่างน้อยหนึ่งฟังก์ชั่นเสมือนจริงประกาศหรือสืบทอด) คุณสามารถใช้มันได้มากกว่าแค่การเหวี่ยงลง - คุณสามารถเหวี่ยงไปด้านข้างหรือแม้กระทั่งโซ่อื่น ๆ dynamic_castจะหาวัตถุที่ต้องการและส่งกลับมาถ้าเป็นไปได้ หากไม่สามารถทำได้จะส่งคืนnullptrในกรณีของตัวชี้หรือส่งstd::bad_castในกรณีของการอ้างอิง

dynamic_castมีข้อ จำกัด บางอย่าง มันจะไม่ทำงานหากมีวัตถุหลายประเภทที่เหมือนกันในลำดับชั้นการสืบทอด (ที่เรียกว่า 'เพชรที่น่ากลัว') และคุณไม่ได้ใช้การvirtualสืบทอด นอกจากนี้ยังสามารถผ่านการรับมรดกสาธารณะเท่านั้น - จะไม่สามารถเดินทางผ่านprotectedหรือprivateรับมรดกได้เสมอ อย่างไรก็ตามนี่เป็นปัญหาที่ไม่ค่อยเกิดขึ้นเนื่องจากรูปแบบของการสืบทอดนั้นหายาก


reinterpret_castเป็นนักแสดงที่อันตรายที่สุดและควรใช้อย่างไม่ จำกัด เปลี่ยนประเภทหนึ่งเป็นประเภทอื่นโดยตรง - เช่นการคัดเลือกค่าจากตัวชี้หนึ่งไปยังอีกประเภทหนึ่งหรือเก็บตัวชี้ไว้ในintหรือสิ่งแปลก ๆ อื่น ๆ ทุกประเภท ส่วนใหญ่การรับประกันเดียวที่คุณได้รับreinterpret_castคือโดยปกติถ้าคุณส่งผลลัพธ์กลับไปเป็นประเภทเดิมคุณจะได้รับค่าเดียวกันแน่นอน (แต่ไม่ใช่ถ้าประเภทกลางมีขนาดเล็กกว่าประเภทดั้งเดิม) มีจำนวนการแปลงที่reinterpret_castไม่สามารถทำได้เช่นกัน ส่วนใหญ่จะใช้สำหรับการแปลงแปลก ๆ และการปรับเปลี่ยนบิตเช่นเปลี่ยนกระแสข้อมูลดิบเป็นข้อมูลจริงหรือเก็บข้อมูลในบิตต่ำของตัวชี้ไปยังข้อมูลที่จัดตำแหน่ง


นักแสดงสไตล์ Cและนักแสดงสไตล์ฟังก์ชั่นมีการใช้งาน(type)objectหรือtype(object)ตามลำดับและเทียบเท่าฟังก์ชั่น พวกเขาถูกกำหนดให้เป็นคนแรกของต่อไปนี้ซึ่งประสบความสำเร็จ:

  • const_cast
  • static_cast (แม้ว่าจะเพิกเฉยต่อข้อ จำกัด การเข้าถึง)
  • static_cast (ดูด้านบน) แล้ว const_cast
  • reinterpret_cast
  • reinterpret_castจากนั้น const_cast

ดังนั้นจึงสามารถใช้แทนการปลดเปลื้องอื่น ๆ ได้ในบางกรณี แต่อาจเป็นอันตรายอย่างยิ่งเนื่องจากความสามารถในการเปลี่ยนเป็น a reinterpret_castและควรเลือกใช้หลังเมื่อต้องการการร่ายอย่างชัดเจนเว้นแต่คุณแน่ใจว่าstatic_castจะสำเร็จหรือreinterpret_castล้มเหลว . ถึงกระนั้นลองพิจารณาตัวเลือกที่ยาวและชัดเจนมากขึ้น

static_castcast -style casts ยังละเว้นการควบคุมการเข้าถึงเมื่อทำการ a ซึ่งหมายความว่าพวกมันมีความสามารถในการดำเนินการที่ไม่มี cast อื่น ๆ แม้ว่านี่จะเป็นกระบอง แต่ในใจของฉันก็เป็นอีกสาเหตุหนึ่งที่ทำให้หลีกเลี่ยงการแสดงใน C


17
dynamic_cast สำหรับประเภท polymorphic เท่านั้น คุณจะต้องใช้มันเมื่อคุณส่งไปยังคลาสที่ได้รับ static_cast เป็นตัวเลือกแรกอย่างแน่นอนถ้าคุณไม่ต้องการฟังก์ชั่นการทำงานของ dynamic_cast มันไม่ใช่กระสุน "การตรวจสอบประเภทตัวพิมพ์" ที่น่าอัศจรรย์โดยทั่วไป
jalf

2
คำตอบที่ดี! หนึ่งคำพูดอย่างรวดเร็ว: static_cast อาจจำเป็นต้องสร้างลำดับชั้นในกรณีที่คุณมี Derived * & เพื่อแปลงเป็น Base * & เนื่องจากตัวชี้สองตัว / การอ้างอิงไม่ได้สร้างลำดับชั้นโดยอัตโนมัติ ฉันเจอสถานการณ์ดังกล่าว (ตรงไปตรงมาไม่ธรรมดา) เมื่อสองนาทีก่อน ;-)
bartgol

5
* "ไม่มีนักแสดง C ++ คนอื่นที่สามารถลบconstได้ (ไม่ถึงreinterpret_cast)" ... จริงเหรอ เกี่ยวกับreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))อะไร
user541686

29
ฉันคิดว่ารายละเอียดที่สำคัญที่ขาดหายไปข้างต้นคือ dynamic_cast มีบทลงโทษด้านประสิทธิภาพในการทำงานเมื่อเปรียบเทียบกับ static หรือ reinterpret_cast สิ่งนี้มีความสำคัญเช่นในซอฟต์แวร์แบบเรียลไทม์
jfritz42

5
อาจคุ้มค่าที่จะกล่าวถึงซึ่งreinterpret_castมักเป็นอาวุธที่เลือกใช้เมื่อจัดการกับชุดข้อมูลทึบแสงของ API
Class Skeleton

333

ใช้dynamic_castสำหรับการแปลงพอยน์เตอร์ / การอ้างอิงภายในลำดับชั้นการสืบทอด

ใช้static_castสำหรับการแปลงประเภทธรรมดา

ใช้reinterpret_castสำหรับการตีความรูปแบบบิตในระดับต่ำอีกครั้ง ใช้ด้วยความระมัดระวังอย่างยิ่ง

ใช้สำหรับการหล่อออกไปconst_cast const/volatileหลีกเลี่ยงสิ่งนี้หากคุณไม่ได้ใช้ API ที่ไม่ถูกต้อง


2
ระวังด้วย dynamic_cast มันอาศัย RTTI และสิ่งนี้จะไม่ทำงานตามที่คาดไว้ในขอบเขตของไลบรารีที่แบ่งใช้ เพียงเพราะคุณสร้างไลบรารี่ที่สามารถเรียกใช้และแชร์ได้อย่างอิสระไม่มีวิธีมาตรฐานในการซิงค์ RTTI กับบิลด์ที่ต่างกัน ด้วยเหตุนี้ในไลบรารี Qt จึงมี qobject_cast <> ซึ่งใช้ข้อมูลประเภท QObject สำหรับการตรวจสอบประเภท
user3150128

198

(มีการอธิบายทางทฤษฎีและแนวคิดมากมาย)

ด้านล่างนี้คือบางส่วนของตัวอย่างการปฏิบัติเมื่อใช้static_cast , dynamic_cast , const_cast , reinterpret_cast

(อ้างถึงสิ่งนี้เพื่อเข้าใจการอธิบาย: 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
ทฤษฏีของคำตอบอื่น ๆ นั้นดี แต่ก็ยังทำให้สับสนเมื่อได้เห็นตัวอย่างเหล่านี้หลังจากอ่านคำตอบอื่น ๆ แล้วทำให้พวกเขาทุกคนมีเหตุผล นั่นคือไม่มีตัวอย่างฉันยังไม่แน่ใจ แต่กับพวกเขาตอนนี้ฉันแน่ใจเกี่ยวกับความหมายของคำตอบอื่น ๆ
Solx

1
เกี่ยวกับการใช้ reinterpret_cast ครั้งล่าสุด: สิ่งนี้ไม่เหมือนกับการใช้งานstatic_cast<char*>(&val)หรือไม่
Lorenzo Belli

3
@ LorenzoBelli แน่นอนไม่ คุณลองหรือยัง หลังไม่ถูกต้อง C ++ และการคอมไพล์บล็อก ทำงานเฉพาะระหว่างประเภทกับการแปลงที่กำหนดความสัมพันธ์ที่มองเห็นได้โดยการรับมรดกหรือยังstatic_cast / จาก void *สำหรับทุกอย่างมีการปลดเปลื้องอื่น ๆ ประเภทreinterpret castใดก็ได้ที่char *ได้รับอนุญาตให้อ่านการเป็นตัวแทนของวัตถุใด ๆ - และหนึ่งในกรณีเดียวที่คำหลักนั้นมีประโยชน์ไม่ใช่เครื่องกำเนิดอาละวาดของพฤติกรรมการใช้งาน / ไม่ได้กำหนด แต่นี้จะไม่ถือว่าเป็น 'ปกติ' แปลงจึงไม่ได้รับอนุญาตตาม (ปกติ) static_castอนุรักษ์นิยมมาก
underscore_d

2
reinterpret_cast ค่อนข้างธรรมดาเมื่อคุณทำงานกับซอฟต์แวร์ระบบเช่นฐานข้อมูล กรณีส่วนใหญ่คุณเขียนผู้จัดการหน้าของคุณเองซึ่งไม่มีความคิดเกี่ยวกับประเภทข้อมูลที่เก็บไว้ในหน้าและเพียงแค่ส่งกลับตัวชี้โมฆะ มันขึ้นอยู่กับระดับที่สูงขึ้นในการทำการตีความนักแสดงซ้ำและสรุปมันตามที่พวกเขาต้องการ
Sohaib

1
ตัวอย่าง const_cast จัดแสดงพฤติกรรมที่ไม่ได้กำหนด ตัวแปรที่ประกาศเป็น const ไม่สามารถ de-const-ed อย่างไรก็ตามตัวแปรที่ประกาศเป็น non-const ที่ถูกส่งผ่านไปยังฟังก์ชันที่ทำการอ้างอิง const สามารถในฟังก์ชันนั้นเป็น de-const-ed โดยที่ไม่ใช่ UB
Johann Gerell

99

มันอาจช่วยได้ถ้าคุณรู้เรื่องภายในเล็กน้อย ...

static_cast

  • คอมไพเลอร์ C ++ รู้วิธีการแปลงระหว่างประเภทของ Scaler เช่น float เป็น int ใช้static_castสำหรับพวกเขา
  • เมื่อคุณถามคอมไพเลอร์การแปลงจากประเภทAการB, static_castโทรBคอนสตรัค 's ผ่านAเป็นพารามิเตอร์ หรือAอาจมีตัวดำเนินการแปลง (เช่นA::operator B()) หากBไม่มีคอนสตรัคเตอร์ดังกล่าวหรือAไม่มีโอเปอเรเตอร์การแปลงคุณจะได้รับข้อผิดพลาดในการรวบรวมเวลา
  • ส่งจากA*การB*เสมอประสบความสำเร็จถ้า A และ B อยู่ในลำดับชั้นมรดก (หรือเป็นโมฆะ) มิฉะนั้นคุณจะได้รับการรวบรวมข้อผิดพลาด
  • Gotcha : ถ้าคุณโยนตัวชี้ฐานไปยังตัวชี้ที่ได้รับ แต่ถ้าวัตถุจริงไม่ใช่ชนิดที่ได้รับจริง ๆ แล้วคุณจะไม่ได้รับข้อผิดพลาด คุณได้รับตัวชี้ที่ไม่ดีและมีโอกาสมากที่ segfault ในขณะทำงาน กันไปสำหรับการA&B&
  • Gotcha : Cast จาก Derived to Base หรือ viceversa สร้างสำเนาใหม่ ! สำหรับคนที่มาจาก C # / Java สิ่งนี้อาจเป็นเรื่องน่าประหลาดใจอย่างมากเพราะผลที่ได้คือวัตถุที่ถูกตัดออกมาจาก Derived

dynamic_cast

  • dynamic_cast ใช้ข้อมูลประเภทรันไทม์เพื่อคำนวณว่าการส่งนั้นถูกต้องหรือไม่ ยกตัวอย่างเช่น(Base*)การ(Derived*)อาจล้มเหลวหากตัวชี้ไม่จริงชนิดที่ได้มา
  • ซึ่งหมายความว่า dynamic_cast มีราคาแพงมากเมื่อเทียบกับ static_cast!
  • สำหรับA*ถึงB*หากการส่งไม่ถูกต้อง dynamic_cast จะส่งคืน nullptr
  • สำหรับA&การB&ถ้าหล่อไม่ถูกต้องแล้ว dynamic_cast จะโยนยกเว้น bad_cast
  • ซึ่งแตกต่างจากการปลดเปลื้องอื่น ๆ มีค่าใช้จ่ายรันไทม์

const_cast

  • ในขณะที่ static_cast สามารถทำแบบไม่ใช่ const เพื่อ const มันไม่สามารถไปทางอื่น const_cast สามารถทำได้ทั้งสองวิธี
  • ตัวอย่างหนึ่งที่สิ่งนี้มีประโยชน์คือการวนซ้ำผ่านคอนเทนเนอร์บางอย่างset<T>ซึ่งส่งคืนองค์ประกอบของมันเป็น const เท่านั้นเพื่อให้แน่ใจว่าคุณจะไม่เปลี่ยนคีย์ อย่างไรก็ตามหากคุณต้องการแก้ไขสมาชิกที่ไม่ได้ใช้คีย์ของวัตถุคุณควรจะยอมรับ คุณสามารถใช้ const_cast เพื่อลบความขุ่น
  • อีกตัวอย่างหนึ่งคือเมื่อคุณต้องการที่จะดำเนินการเช่นเดียวกับT& SomeClass::foo() const T& SomeClass::foo() constเพื่อหลีกเลี่ยงการทำสำเนารหัสคุณสามารถใช้ const_cast เพื่อคืนค่าของฟังก์ชันหนึ่งจากอีกฟังก์ชันหนึ่ง

reinterpret_cast

  • สิ่งนี้บอกว่าใช้ไบต์เหล่านี้ที่ตำแหน่งหน่วยความจำนี้และคิดว่ามันเป็นวัตถุที่กำหนด
  • ตัวอย่างเช่นคุณสามารถโหลด 4 ไบต์ลอย 4 ไบต์ของ int เพื่อดูว่าบิตในลอยดูเหมือนว่า
  • เห็นได้ชัดว่าหากข้อมูลไม่ถูกต้องสำหรับประเภทคุณอาจได้รับ segfault
  • ไม่มีค่าใช้จ่ายรันไทม์สำหรับนักแสดงนี้

ฉันเพิ่มข้อมูลผู้ดำเนินการแปลง แต่มีอีกสองสามสิ่งที่ควรแก้ไขเช่นกันและฉันรู้สึกไม่สบายใจที่จะอัปเดตสิ่งนี้มากเกินไป รายการคือ: 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.คุณได้รับ UB ซึ่งอาจส่งผลให้ segfault ที่รันไทม์หากคุณโชคดี 2. Dynamic casts สามารถใช้ในการร่ายข้ามได้ 3. การปลดเปลื้อง Const อาจส่งผลให้ UB ในบางกรณี การใช้mutableอาจเป็นตัวเลือกที่ดีกว่าในการใช้ความมั่นคงทางตรรกะ
เอเดรียน

1
@ เอเดรียคุณถูกต้องในการนับทั้งหมด คำตอบนั้นเขียนขึ้นสำหรับผู้เริ่มต้นตั้งแต่ระดับเริ่มต้นขึ้นไปและฉันไม่ต้องการที่จะเอาชนะพวกเขาด้วยภาวะแทรกซ้อนอื่น ๆ ที่มาพร้อมกับmutableการคัดเลือกข้าม ฯลฯ
Shital Shah

16

ทำสิ่งนี้ตอบคำถามของคุณ?

ฉันไม่เคยใช้reinterpret_castและสงสัยว่าทำงานในกรณีที่ต้องการมันไม่ได้เป็นกลิ่นของการออกแบบที่ไม่ดี ในฐานรหัสฉันทำงานอยู่dynamic_castมีการใช้งานมาก ข้อแตกต่าง static_castคือการdynamic_castตรวจสอบรันไทม์ซึ่งอาจ (ปลอดภัยกว่า) หรืออาจจะไม่ (เพิ่มมากขึ้น) เป็นสิ่งที่คุณต้องการ (ดูmsdn )


3
ฉันใช้ reintrepret_cast เพื่อจุดประสงค์เดียว - ดึงบิตออกมาจากสองเท่า (ขนาดเดียวกันยาวบนแพลตฟอร์มของฉัน)
Joshua

2
จำเป็นต้องมี reinterpret_cast เช่นทำงานกับวัตถุ COM CoCreateInstance () มีพารามิเตอร์เอาต์พุตเป็น type void ** (พารามิเตอร์สุดท้าย) ซึ่งคุณจะผ่านตัวชี้ที่ประกาศเป็นเช่น "INetFwPolicy2 * pNetFwPolicy2" ในการทำเช่นนั้นคุณต้องเขียนอะไรบางอย่างเช่น reinterpret_cast <void **> (& pNetFwPolicy2)
Serge Rogatch

1
อาจมีวิธีที่แตกต่างกัน แต่ฉันใช้reinterpret_castเพื่อแยกข้อมูลออกจากอาร์เรย์ ตัวอย่างเช่นถ้าฉันมีchar*บัฟเฟอร์ขนาดใหญ่ที่เต็มไปด้วยข้อมูลไบนารีที่อัดแน่นที่ฉันจำเป็นต้องย้ายผ่านและรับชนิดดั้งเดิมที่แตกต่างกันไป สิ่งนี้: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

ฉันไม่เคยใช้reinterpret_castมันมีประโยชน์ไม่มากนัก
Pika พ่อมดแห่งปลาวาฬ

ส่วนตัวฉันเคยเห็นreinterpret_castใช้เพียงเหตุผลเดียว ฉันเคยเห็นข้อมูลวัตถุดิบที่เก็บไว้ในประเภทข้อมูล "หยด" ในฐานข้อมูลจากนั้นเมื่อข้อมูลถูกดึงจากฐานข้อมูลreinterpret_castจะใช้ในการเปลี่ยนข้อมูลดิบนี้เป็นวัตถุ
ImaginaryHuman072889

15

นอกเหนือจากคำตอบอื่น ๆ จนถึงตอนนี้นี่คือตัวอย่างที่static_castไม่ชัดเจนที่ไม่เพียงพอดังนั้นจึงreinterpret_castเป็นสิ่งจำเป็น สมมติว่ามีฟังก์ชั่นซึ่งในพารามิเตอร์ขาออกส่งกลับพอยน์เตอร์ไปยังวัตถุของคลาสที่แตกต่างกัน (ซึ่งไม่ได้แชร์คลาสพื้นฐานทั่วไป) ตัวอย่างจริงของฟังก์ชันดังกล่าวคือCoCreateInstance()(ดูพารามิเตอร์สุดท้ายซึ่งอันที่จริงแล้วvoid**) สมมติว่าคุณร้องขอคลาสของวัตถุเฉพาะจากฟังก์ชันนี้เพื่อให้คุณทราบล่วงหน้าถึงชนิดของตัวชี้ (ซึ่งคุณมักจะทำกับวัตถุ COM) ในกรณีนี้คุณไม่สามารถโยนตัวชี้ไปยังตัวชี้ของคุณเข้าvoid**กับstatic_cast: reinterpret_cast<void**>(&yourPointer)คุณจำเป็นต้อง

ในรหัส:

#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) );

อย่างไรก็ตามstatic_castทำงานได้กับพอยน์เตอร์ง่าย ๆ (ไม่ใช่พอยน์เตอร์ถึงพอยน์เตอร์) ดังนั้นโค้ดข้างต้นสามารถเขียนใหม่เพื่อหลีกเลี่ยงreinterpret_cast(ในราคาของตัวแปรพิเศษ) ด้วยวิธีต่อไปนี้:

#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)แทนที่จะstatic_cast<void**>(&pNetFwPolicy2)?
jp48

9

ในขณะที่คำตอบอื่น ๆ ที่อธิบายไว้อย่างแตกต่างระหว่าง C ++ ปลดเปลื้องผมอยากจะเพิ่มบันทึกสั้น ๆ ว่าทำไมคุณไม่ควรใช้บรรยากาศแบบ C และ(Type) varType(var)

สำหรับผู้เริ่มต้น C + + C-style casts ดูเหมือนว่าเป็นการดำเนินการ superset บน C ++ casts (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) และบางคนอาจชอบพวกมันมากกว่า C ++ casts . อันที่จริงแล้วการหล่อแบบ C นั้นเป็นแบบซูเปอร์เซ็ตและสั้นกว่าในการเขียน

ปัญหาหลักของ C-style casts คือพวกเขาซ่อนเจตนาที่แท้จริงของนักพัฒนา cast-style cast สามารถทำการร่ายได้ทุกประเภทจากการร่ายความปลอดภัยตามปกติโดย static_cast <> () และ dynamic_cast <> () ถึงการร่ายที่อาจเป็นอันตรายเช่น const_cast <> () ซึ่งตัวดัดแปลง const สามารถลบได้ดังนั้นตัวแปร const สามารถแก้ไขและ reinterpret_cast <> () ที่สามารถตีความค่าจำนวนเต็มเป็นตัวชี้ได้อีกครั้ง

นี่คือตัวอย่าง

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 ++ ในภาษานั้นเพื่อให้นักพัฒนาสามารถอธิบายความตั้งใจของเขาได้ - ทำไมเขาถึงต้องลงมือทำ ด้วยการใช้งาน C-style casts ที่ใช้ได้อย่างสมบูรณ์ใน C ++ คุณจะทำให้โค้ดของคุณอ่านน้อยลงและเกิดข้อผิดพลาดได้ง่ายขึ้นโดยเฉพาะสำหรับนักพัฒนาอื่น ๆ ที่ไม่ได้สร้างรหัสของคุณ ดังนั้นเพื่อให้โค้ดของคุณอ่านง่ายและชัดเจนคุณควรเลือก C + + casts มากกว่า C-style casts

นี่คือคำพูดสั้น ๆ จากหนังสือ Bjarne Stroustrup (ผู้เขียน C ++) ภาษาการเขียนโปรแกรม C ++ รุ่นที่ 4 - หน้า 302

คาสต์สไตล์ C นี้มีอันตรายมากกว่าตัวดำเนินการแปลงที่ตั้งชื่อไว้เนื่องจากสัญกรณ์นั้นยากที่จะสังเกตเห็นในโปรแกรมขนาดใหญ่และประเภทของการแปลงที่โปรแกรมเมอร์ตั้งใจไม่ชัดเจน


5

เพื่อความเข้าใจลองพิจารณาตัวอย่างโค้ดด้านล่าง:

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;
}

รวบรวมเฉพาะบรรทัด (4) โดยไม่มีข้อผิดพลาด reinterpret_castเท่านั้นที่สามารถใช้ในการแปลงตัวชี้ไปยังวัตถุเป็นตัวชี้ไปเป็นชนิดวัตถุใด ๆ ที่ไม่เกี่ยวข้อง

สิ่งนี้ที่ควรสังเกตคือ: dynamic_castจะล้มเหลวในเวลาทำงานอย่างไรก็ตามในคอมไพเลอร์ส่วนใหญ่มันก็จะล้มเหลวในการคอมไพล์เพราะไม่มีหน้าที่เสมือนในโครงสร้างของตัวชี้ที่กำลังส่งซึ่งหมายถึงdynamic_castจะทำงานกับพอยน์เตอร์พหุนาม .

เมื่อใดที่จะใช้ C ++ cast :

  • ใช้static_castเป็นค่าที่เทียบเท่ากับการใช้ C-style ในการแปลงค่าหรือเมื่อเราต้องการพอยเตอร์ของตัวชี้จากคลาสไปยังซูเปอร์คลาส
  • ใช้const_castเพื่อลบตัวระบุ const
  • ใช้reinterpret_castเพื่อทำการแปลงประเภทตัวชี้ไปยังและจากจำนวนเต็มและชนิดตัวชี้อื่น ๆ ที่ไม่ปลอดภัย ใช้สิ่งนี้เฉพาะเมื่อเรารู้ว่าเรากำลังทำอะไรและเราเข้าใจปัญหาเรื่องนามแฝง

2

static_castvs dynamic_castvs reinterpret_castinternals ดูบน downcast / upcast

ในคำตอบนี้ฉันต้องการเปรียบเทียบกลไกทั้งสามนี้ในตัวอย่างที่เป็นรูปธรรม upcast / downcast และวิเคราะห์สิ่งที่เกิดขึ้นกับพอยน์เตอร์ / หน่วยความจำ / แอสเซมบลีพื้นฐานเพื่อให้เข้าใจอย่างเป็นรูปธรรมเกี่ยวกับวิธีเปรียบเทียบ

ฉันเชื่อว่าสิ่งนี้จะให้สัญชาตญาณที่ดีในการปลดเปลื้องเหล่านั้นแตกต่างกัน

  • static_cast: ทำการชดเชยหนึ่งแอดเดรสที่รันไทม์ (ผลกระทบจากรันไทม์ต่ำ) และไม่มีการตรวจสอบความปลอดภัยว่าดาวน์ไลท์นั้นถูกต้องหรือไม่

  • dyanamic_cast: ที่อยู่เดียวกันตรงข้ามกับรันไทม์เช่นstatic_castกัน แต่ยังรวมถึงการตรวจสอบด้านความปลอดภัยที่มีราคาแพงว่า downcast นั้นถูกต้องโดยใช้ RTTI

    การตรวจสอบความปลอดภัยนี้ช่วยให้คุณสามารถสอบถามได้ว่าตัวชี้คลาสพื้นฐานเป็นประเภทที่กำหนดเมื่อใช้งานจริงหรือไม่โดยการตรวจสอบการส่งคืนnullptrซึ่งบ่งชี้ว่าการดาวน์สตรีมที่ไม่ถูกต้อง

    ดังนั้นหากรหัสของคุณไม่สามารถตรวจสอบได้nullptrและดำเนินการไม่ยกเลิกที่ถูกต้องคุณควรใช้static_castแทนการส่งแบบไดนามิก

    หากการยกเลิกเป็นการกระทำเดียวที่โค้ดของคุณสามารถทำได้คุณอาจต้องการเปิดใช้งานการdynamic_castดีบักบิวด์ ( -NDEBUG) และใช้static_castอย่างอื่นเช่นที่ทำที่นี่เพื่อไม่ให้การรันเร็วของคุณช้าลง

  • reinterpret_cast: ไม่ทำอะไรเลยขณะรันไทม์หรือแม้แต่ออฟเซ็ตที่อยู่ ตัวชี้ต้องชี้ไปที่ประเภทที่ถูกต้องไม่แม้แต่คลาสพื้นฐานทำงาน โดยทั่วไปคุณไม่ต้องการสิ่งนี้เว้นแต่ว่ามีการสตรีมข้อมูลดิบไบต์

พิจารณาตัวอย่างรหัสต่อไปนี้:

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;
}

รวบรวมเรียกใช้และถอดแยกชิ้นส่วนด้วย:

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

ที่setarchจะใช้ในการปิดการใช้งาน ASLRเพื่อให้ง่ายต่อการเปรียบเทียบการทำงาน

เอาต์พุตที่เป็นไปได้:

&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

ตอนนี้ดังที่กล่าวไว้ที่: https://en.wikipedia.org/wiki/Virtual_method_tableเพื่อสนับสนุนการเรียกใช้เมธอดเสมือนอย่างมีประสิทธิภาพโครงสร้างข้อมูลหน่วยความจำของDต้องมีลักษณะดังนี้:

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

ความจริงที่สำคัญคือโครงสร้างข้อมูลของหน่วยความจำDที่มีอยู่ข้างในโครงสร้างหน่วยความจำที่เข้ากันได้กับที่ของB1และของB2ภายใน

ดังนั้นเราถึงข้อสรุปที่สำคัญ:

upcast หรือ downcast เพียงต้องการเปลี่ยนค่าตัวชี้โดยค่าที่รู้จักในเวลารวบรวม

ด้วยวิธีนี้เมื่อDถูกส่งผ่านไปยังอาเรย์ประเภทฐานนักแสดงประเภทจะคำนวณว่าออฟเซ็ตและชี้บางสิ่งที่ดูเหมือนว่าถูกต้องB2ในหน่วยความจำ:

b2s[1] = &d;

ยกเว้นว่าอันนี้มี vtable สำหรับDแทนB2และดังนั้นการโทรเสมือนทั้งหมดทำงานอย่างโปร่งใส

ตอนนี้ในที่สุดเราก็สามารถกลับไปพิมพ์แบบหล่อและวิเคราะห์ตัวอย่างที่เป็นรูปธรรมของเราได้

จากเอาต์พุต stdout เราเห็น:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

ดังนั้นการstatic_castกระทำโดยปริยายจึงทำการคำนวณออฟเซ็ตจากDโครงสร้างข้อมูลแบบเต็มอย่างถูกต้องที่ 0x7fffffffc930 ไปยังสิ่งที่B2คล้ายกันซึ่งอยู่ที่ 0x7fffffffcc940 นอกจากนี้เรายังอนุมานว่าสิ่งที่อยู่ระหว่าง 0x7fffffffc930 และ 0x7fffffffc940 นั้นน่าจะเป็นB1ข้อมูลและ vtable

จากนั้นในส่วน downcast ตอนนี้มันง่ายที่จะเข้าใจว่าสิ่งที่ไม่ถูกต้องล้มเหลวและทำไม:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: คอมไพเลอร์เพิ่งเพิ่มขึ้น 0x10 ที่ไบต์เวลารวบรวมเพื่อลองจากB2ไปยังที่มีD

    แต่เนื่องจากb2s[0]ไม่ใช่ a Dตอนนี้จึงชี้ไปยังขอบเขตหน่วยความจำที่ไม่ได้กำหนด

    ถอดชิ้นส่วนคือ:

    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)

    ดังนั้นเราจึงเห็นว่า GCC ทำ:

    • ตรวจสอบว่าตัวชี้เป็น NULL และถ้าใช่คืน NULL
    • ไม่เช่นนั้นให้ลบ 0x10 จากนั้นถึงสิ่งDที่ไม่มีอยู่
  • dynamic_cast<D*>(b2s[0]) 0: C ++ พบจริงว่าตัวละครนั้นไม่ถูกต้องและกลับมาแล้วnullptr!

    ไม่มีวิธีนี้สามารถทำได้ในเวลารวบรวมและเราจะยืนยันว่าจากการถอดชิ้นส่วน:

    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)

    ครั้งแรกที่มีการตรวจสอบเป็นโมฆะและมันกลับเป็นโมฆะถ้า einput ที่เป็นโมฆะ

    มิฉะนั้นจะตั้งค่าการขัดแย้งบางอย่างใน RDX, RSI และ RDI __dynamic_castและบริการโทร

    ฉันไม่มีความอดทนที่จะวิเคราะห์สิ่งนี้เพิ่มเติมในขณะนี้ แต่อย่างที่คนอื่นพูดวิธีเดียวที่จะใช้งานได้คือ__dynamic_castการเข้าถึงโครงสร้างข้อมูลในหน่วยความจำ RTTI พิเศษที่แสดงถึงลำดับชั้นของชั้นเรียน

    ดังนั้นจึงต้องเริ่มต้นจากB2รายการสำหรับตารางที่แล้วเดินลำดับชั้นนี้จนกว่าจะพบว่า vtable สำหรับDtypecast b2s[0]จาก

    นี่คือเหตุผลที่นักแปลตีความซ้ำอาจมีราคาแพง! ที่นี่คือตัวอย่างที่แพทช์ซับหนึ่งแปลง a dynamic_castเป็น a static_castในโปรเจ็กต์ที่ซับซ้อนลดรันไทม์ 33%! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940อันนี้แค่เชื่อว่าเราสุ่มสี่สุ่มห้า: เราบอกว่ามีDที่อยู่b2s[1]และคอมไพเลอร์ไม่มีการคำนวณออฟเซ็ต

    แต่นี่เป็นสิ่งที่ผิดเพราะ D จริงๆแล้วที่ 0x7fffffffc930 สิ่งที่อยู่ที่ 0x7fffffffc940 คือโครงสร้าง B2 ที่เหมือนภายใน D! ดังนั้นการเข้าถึงถังขยะ

    เราสามารถยืนยันสิ่งนี้จากความสยดสยอง -O0ชุมนุมที่ที่เพิ่งจะย้ายค่าไปรอบ ๆ :

    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)

คำถามที่เกี่ยวข้อง:

ทดสอบกับ Ubuntu 18.04 amd64, GCC 7.4.0

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.