การแปลงวิธี C ++ เป็นฟังก์ชัน C ด้วยอาร์กิวเมนต์ตัวชี้เป็นรูปแบบที่ยอมรับได้หรือไม่


16

ฉันใช้ C ++ กับ ESP-32 เมื่อลงทะเบียนตัวจับเวลาฉันต้องทำสิ่งนี้:

timer_args.callback = reinterpret_cast<esp_timer_cb_t>(&SoundMixer::soundCallback);
timer_args.arg = this;

soundCallbackนี่โทรจับเวลา

และสิ่งเดียวกันเมื่อลงทะเบียนงาน:

xTaskCreate(reinterpret_cast<TaskFunction_t>(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle);

ดังนั้นวิธีการเริ่มต้นในงานที่แยกจากกัน

GCC เตือนฉันเสมอเกี่ยวกับการแปลงเหล่านี้ แต่ทำงานได้ตามแผนที่วางไว้

เป็นที่ยอมรับในรหัสการผลิตหรือไม่ มีวิธีที่ดีกว่าในการทำเช่นนี้?

คำตอบ:


47

A reinterpret_castมักจะเป็นปลาถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่ ที่นี่รหัสของคุณจะทำงานได้เฉพาะเนื่องจากการเรียก GCC สำหรับวิธีการ C ++ แต่นี่มีกลิ่นอย่างมากเช่นพฤติกรรมที่ไม่ได้กำหนด โดยเฉพาะอย่างยิ่งคุณไม่ควรสันนิษฐานว่าฟังก์ชั่นสมาชิกใด ๆ เข้ากันได้กับพอยน์เตอร์ฟังก์ชั่นปกติ

วิธีการปกติจะเป็นการกำหนดฟังก์ชั่นที่เข้ากันได้กับ C แทนลายเซ็นที่เหมาะสมซึ่งเรียกวิธีการ C ++ ภายใน ตัวอย่างเช่น:

extern "C" static void my_timer_callback(void* arg) {
  static_cast<SoundMixer*>(arg)->soundCallback();
}

การร่ายนี้ทำได้ดีเพราะเราทำการส่งกลับจาก a void*ไปยังชนิดของวัตถุที่แหลมไป

รายละเอียด:

  • extern "C"ระบุการเชื่อมโยงภาษาของฟังก์ชั่นนี้ การเชื่อมโยงภาษามีผลกับการจัดการชื่อและการเรียกใช้ฟังก์ชัน ฟังก์ชั่นสมาชิกไม่สามารถเชื่อมโยงภาษา C การเชื่อมโยงภาษาส่วนใหญ่ตั้งฉากกับการเชื่อมโยงภายใน / ภายนอก

  • สำหรับการโทรกลับฟังก์ชั่นอาจเป็น "ส่วนตัว" เช่นมีการเชื่อมโยงภายใน รหัส C ไม่เคยอ้างถึงการโทรกลับตามชื่อ ข้อมูลโค้ดข้างต้นระบุการเชื่อมโยงภายในผ่านstaticคำหลัก (ไม่ใช่วิธีคงที่!) อีกทางหนึ่งฟังก์ชั่นอาจถูกวางไว้ใน namespace ที่ไม่ระบุชื่อ

    ฉันไม่แน่ใจทั้งหมดเกี่ยวกับการโต้ตอบระหว่างextern "C"และstatic(การเชื่อมโยงภายใน) เช่น[dcl.link]บอกว่า“ ทุกประเภทฟังก์ชั่นชื่อฟังก์ชั่นที่มีการเชื่อมโยงภายนอกและชื่อตัวแปรที่มีการเชื่อมโยงภายนอกมีการเชื่อมโยงภาษา.” ฉันตีความนี้เพื่อให้ประเภทของการmy_timer_callbackเชื่อมโยงภาษา C มี แต่ชื่อฟังก์ชั่นไม่

  • static_castมีความเหมาะสมที่นี่เพราะเรารู้ชนิดที่แท้จริงของargแต่ไม่สามารถแสดงได้ภายในระบบการพิมพ์ ในทางตรงกันข้าม a reinterpret_castมีความเหมาะสมเมื่อเราต้องการตีความรูปแบบบิตอีกครั้งเช่นตัวชี้ไปยังประเภทตัวเลข

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


1
ไม่std::bindถือว่าตัวชี้วัตถุเป็นอาร์กิวเมนต์วิธีแรกหรือไม่
val พูดว่า Reinstate Monica

5
@val ใช่ แต่นั่นไม่ได้หมายความว่าฟังก์ชั่นสมาชิกเข้ากันได้กับฟังก์ชั่นทั่วไปเพียงว่า bind () ใช้อัลกอริทึม INVOKEที่จัดการฟังก์ชั่นสมาชิกเป็นกรณีแยกจากวัตถุฟังก์ชั่นสามัญรวม พอยน์เตอร์ฟังก์ชั่น เนื่องจาก std :: bind () สร้าง functor ไม่เหมาะสำหรับการเชื่อมกับ C.
amon

1
คำถามอื่น: ทำไมฉันต้องการextern "C"ที่นี่ การเชื่อมโยง C มีความสำคัญในกรณีนี้หรือไม่
val พูดว่า Reinstate Monica

5
@val หากคุณต้องการให้สามารถเรียกใช้ฟังก์ชันนั้นจาก C ได้นั้นจะต้องใช้หลักการเรียก C สิ่งนี้สามารถทำได้โดยการประกาศฟังก์ชั่นที่มีการเชื่อมโยงภาษา C หรือโดยเฉพาะส่วนขยายคอมไพเลอร์ (เช่น__attribute__((cdecl))แต่โปรดอย่าทำอย่างนั้น) ฟังก์ชั่น C ++ ไม่รับประกันว่าจะมีรูปแบบการโทรที่เข้ากันได้กับ C เป็นอย่างอื่น (แต่ใน GCC โดยทั่วไปแล้วจะใช้งานได้ดี)
amon

4
@val สำหรับรายละเอียดว่าเพราะเหตุใดจึงextern "C"มีความจำเป็นอย่างเป็นทางการให้ดู[dcl.link]"ฟังก์ชั่นสองประเภทที่มีการเชื่อมโยงภาษาไดเรนท์เป็นประเภทที่แตกต่างกันแม้ว่าจะเหมือนกันก็ตาม" และ[expr.call]"การเรียกใช้ฟังก์ชันผ่านนิพจน์ที่มีประเภทฟังก์ชันแตกต่างจากประเภทฟังก์ชันของฟังก์ชันที่เรียกว่าส่งผลให้เกิดพฤติกรรมที่ไม่แน่นอน"
Ben Voigt

-1

โดยส่วนตัวแล้ววิธีที่เข้ากันได้ง่ายใช้งานง่ายและเข้าใจง่ายที่สุดที่ฉันพบคือให้ฟังก์ชั่น "wrapper" เข้ากันได้กับอินเตอร์เฟส C ที่คาดไว้ซึ่งเรียกวิธีการภายใน (และในกรณีที่มันไม่คงที่ สร้างอินสแตนซ์หรือใช้อินสแตนซ์ที่มีอยู่เพื่อทำเช่นนั้น) มันอาจถูกมองว่าเป็นความแตกต่างของรูปแบบการออกแบบอะแดปเตอร์


6
นั่นไม่ใช่สิ่งที่ตอบกลับไปหรือ?
Dronz

1
@Dronz หลังจากอ่านครั้งที่สองใช่มันเป็นส่วนใหญ่ ทันทีที่ฉันอ่านstaticฉันเห็นว่ามันเป็นวิธีการและด้วยเหตุผลบางอย่างไม่ได้ตระหนักว่ามันไม่ผ่านthisตัวชี้เป็นอาร์กิวเมนต์แรก (และการอภิปรายต่อไปนี้เกี่ยวกับการใช้งานของstd::bindมันเสริม) แต่ใช่คุณพูดถูก! (ขออภัยสำหรับคำตอบที่สอง!)
พระเยซูอลอนโซ่ Abad

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