เมื่อใช้ฟังก์ชั่นการโทรกลับใน C ++ ฉันควรจะใช้ตัวชี้ฟังก์ชั่น C-style:
void (*callbackFunc)(int);
หรือฉันควรใช้ประโยชน์จากฟังก์ชั่น std ::
std::function< void(int) > callbackFunc;
เมื่อใช้ฟังก์ชั่นการโทรกลับใน C ++ ฉันควรจะใช้ตัวชี้ฟังก์ชั่น C-style:
void (*callbackFunc)(int);
หรือฉันควรใช้ประโยชน์จากฟังก์ชั่น std ::
std::function< void(int) > callbackFunc;
คำตอบ:
ในระยะสั้นใช้std::function
จนกว่าคุณจะมีเหตุผลที่จะไม่
ตัวชี้ฟังก์ชั่นมีข้อเสียของการไม่สามารถจับภาพบริบทบางอย่าง คุณจะไม่สามารถส่งผ่านฟังก์ชั่นแลมบ์ดาเป็นโทรกลับซึ่งจับตัวแปรบริบทบางอย่าง (แต่จะใช้งานได้หากไม่จับภาพใด ๆ ) การเรียกตัวแปรสมาชิกของวัตถุ (เช่นไม่คงที่) จึงเป็นไปไม่ได้เช่นกันเนื่องจากวัตถุ ( this
-pointer) ต้องถูกจับ (1)
std::function
(ตั้งแต่ C ++ 11) เป็นหลักในการจัดเก็บฟังก์ชั่น (ผ่านมันไปรอบ ๆ ไม่จำเป็นต้องเก็บไว้) ดังนั้นหากคุณต้องการเก็บตัวอย่างการโทรกลับไว้ในตัวแปรสมาชิกอาจเป็นทางเลือกที่ดีที่สุดของคุณ แต่ถ้าคุณไม่เก็บไว้มันเป็น "ตัวเลือกแรก" ที่ดีแม้ว่ามันจะมีข้อเสียของการแนะนำค่าใช้จ่าย (เล็กมาก) เมื่อถูกเรียก (ดังนั้นในสถานการณ์ที่สำคัญมากเกี่ยวกับประสิทธิภาพอาจเป็นปัญหา แต่ส่วนใหญ่ ไม่ควร) เป็นสากลมาก: ถ้าคุณสนใจมากเกี่ยวกับรหัสที่สอดคล้องและอ่านได้และไม่ต้องการคิดเกี่ยวกับทุกทางเลือกที่คุณทำ (เช่นต้องการให้มันง่าย) ใช้std::function
สำหรับทุกหน้าที่คุณผ่าน
ลองนึกถึงตัวเลือกที่สาม: หากคุณกำลังจะใช้ฟังก์ชั่นขนาดเล็กซึ่งจะรายงานบางอย่างผ่านฟังก์ชั่นการโทรกลับที่ให้มาให้พิจารณาพารามิเตอร์เทมเพลตซึ่งอาจเป็นวัตถุที่เรียกได้เช่นตัวชี้ฟังก์ชัน functor แลมบ์ดา a std::function
, ... ข้อเสียเปรียบที่นี่คือฟังก์ชั่น (ด้านนอก) ของคุณจะกลายเป็นแม่แบบ ในทางกลับกันคุณจะได้รับประโยชน์จากการโทรกลับไปยัง inline เนื่องจากโค้ดไคลเอนต์ของฟังก์ชัน (outer) ของคุณ "เห็น" การเรียกไปที่ callback จะเป็นข้อมูลประเภทที่แน่นอน
ตัวอย่างสำหรับรุ่นที่มีพารามิเตอร์เทมเพลต (เขียน&
แทน&&
pre-C ++ 11):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
ดังที่คุณเห็นในตารางต่อไปนี้ทุกคนมีข้อดีและข้อเสีย:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) การแก้ไขปัญหามีอยู่เพื่อเอาชนะข้อ จำกัด นี้ตัวอย่างเช่นการส่งข้อมูลเพิ่มเติมเป็นพารามิเตอร์เพิ่มเติมไปยังฟังก์ชัน (ภายนอก) ของคุณ: myFunction(..., callback, data)
จะเรียกcallback(data)
ใช้ นั่นคือ C-style "callback with arguments" ซึ่งเป็นไปได้ใน C ++ (และตามวิธีที่ใช้อย่างหนักใน WIN32 API) แต่ควรหลีกเลี่ยงเพราะเรามีตัวเลือกที่ดีกว่าใน C ++
(2) เว้นแต่ว่าเรากำลังพูดถึงเทมเพลตของคลาสนั่นคือคลาสที่คุณเก็บฟังก์ชันนั้นเป็นเทมเพลต แต่นั่นหมายความว่าในฝั่งไคลเอ็นต์ประเภทของฟังก์ชั่นจะตัดสินใจชนิดของวัตถุที่เก็บการติดต่อกลับซึ่งแทบจะไม่เคยมีตัวเลือกสำหรับกรณีการใช้งานจริง
(3) สำหรับ pre-C ++ 11 ให้ใช้ boost::function
std::function
ชนิดลบได้หากคุณต้องการ เรียกว่าบริบท)
void (*callbackFunc)(int);
อาจเป็นฟังก์ชั่นโทรกลับ C สไตล์ แต่มันเป็นหนึ่งในการออกแบบที่ไม่ดีอย่างน่ากลัว
การเรียกกลับสไตล์ C ที่ออกแบบมาอย่างดีดูเหมือนว่าvoid (*callbackFunc)(void*, int);
จะมีการvoid*
อนุญาตให้รหัสที่โทรกลับเพื่อรักษาสถานะเกินฟังก์ชั่น การไม่ทำเช่นนี้เป็นการบังคับให้ผู้เรียกเก็บสถานะทั่วโลกซึ่งไม่สุภาพ
std::function< int(int) >
จบลงด้วยการเป็นราคาแพงกว่าการint(*)(void*, int)
ขอร้องในการใช้งานส่วนใหญ่ อย่างไรก็ตามมันเป็นเรื่องยากสำหรับคอมไพเลอร์บางส่วนที่จะอยู่ในบรรทัด มีstd::function
การนำไปใช้งานแบบโคลนนิ่งที่คู่แข่งเรียกใช้ฟังก์ชันการชี้เหนือหัว (ดูที่ 'ตัวแทนที่เร็วที่สุดที่เป็นไปได้' เป็นต้น) ที่อาจนำไปใช้ในห้องสมุด
ตอนนี้ไคลเอนต์ของระบบติดต่อกลับมักจะต้องตั้งค่าทรัพยากรและกำจัดพวกเขาเมื่อมีการสร้างและลบโทรกลับและเพื่อให้ตระหนักถึงอายุการใช้งานของการโทรกลับ void(*callback)(void*, int)
ไม่ได้ให้สิ่งนี้
บางครั้งสิ่งนี้สามารถใช้ได้ผ่านโครงสร้างรหัส (การโทรกลับมี จำกัด อายุการใช้งาน) หรือผ่านกลไกอื่น ๆ (ยกเลิกการลงทะเบียนการโทรกลับและสิ่งที่คล้ายกัน)
std::function
จัดเตรียมวิธีสำหรับการจัดการอายุการใช้งานที่ จำกัด (สำเนาล่าสุดของวัตถุจะหายไปเมื่อถูกลืม)
โดยทั่วไปแล้วฉันจะใช้std::function
จนกว่าจะมีข้อกังวลเกี่ยวกับประสิทธิภาพ ถ้าพวกเขาทำฉันจะมองหาการเปลี่ยนแปลงเชิงโครงสร้าง (แทนการโทรกลับต่อพิกเซลวิธีสร้างโปรเซสเซอร์ scanline จากแลมบ์ดาที่คุณผ่านฉันไปแล้ว) ซึ่งน่าจะเพียงพอที่จะลดค่าใช้จ่ายการเรียกใช้ฟังก์ชันไปสู่ระดับเล็กน้อย ) จากนั้นถ้ามันยังคงอยู่ฉันจะเขียนผู้delegate
ได้รับมอบหมายที่เร็วที่สุดที่เป็นไปได้และดูว่าปัญหาด้านประสิทธิภาพหายไปหรือไม่
ฉันส่วนใหญ่จะใช้ตัวชี้ฟังก์ชั่นสำหรับ API ดั้งเดิมเท่านั้นหรือสำหรับการสร้างอินเตอร์เฟส C สำหรับการสื่อสารระหว่างคอมไพเลอร์ที่สร้างรหัส ฉันยังใช้พวกเขาเป็นรายละเอียดการใช้งานภายในเมื่อฉันใช้ตารางกระโดดประเภทลบ ฯลฯ : เมื่อฉันทั้งการผลิตและการบริโภคและฉันไม่เปิดเผยภายนอกสำหรับรหัสลูกค้าที่จะใช้และตัวชี้ฟังก์ชั่นทำทุกอย่างที่ฉันต้องการ .
โปรดทราบว่าคุณสามารถเขียนชุดที่เปลี่ยนstd::function<int(int)>
เป็นint(void*,int)
รูปแบบการโทรกลับโดยสมมติว่ามีโครงสร้างพื้นฐานการจัดการอายุการใช้งานการโทรกลับที่เหมาะสม ดังนั้นในการทดสอบควันสำหรับระบบการจัดการการโทรกลับแบบ C สไตล์ใด ๆ ฉันจะตรวจสอบให้แน่ใจว่าการห่อนั้นใช้std::function
งานได้ดีพอสมควร
void*
มาจากไหน ทำไมคุณต้องการที่จะรักษาสถานะนอกเหนือจากฟังก์ชั่น? ฟังก์ชั่นควรมีรหัสทั้งหมดที่ต้องการฟังก์ชั่นทั้งหมดคุณเพียงแค่ส่งอาร์กิวเมนต์ที่ต้องการและแก้ไขและส่งคืนบางสิ่ง หากคุณต้องการสถานะภายนอกบางอย่างแล้วทำไม functionPtr หรือ callback จึงพกกระเป๋าใบนั้น ฉันคิดว่าการติดต่อกลับนั้นซับซ้อนโดยไม่จำเป็น
this
แล้ว เป็นเพราะเราต้องคำนึงถึงกรณีของฟังก์ชั่นสมาชิกที่ถูกเรียกดังนั้นเราต้องการthis
ตัวชี้เพื่อชี้ไปยังที่อยู่ของวัตถุ? หากฉันผิดคุณสามารถให้ลิงค์ไปยังที่ที่ฉันสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้เพราะฉันไม่สามารถหาข้อมูลได้มากนัก ขอบคุณล่วงหน้า.
void*
เพื่ออนุญาตให้ส่งผ่านสถานะรันไทม์ ตัวชี้ฟังก์ชั่นที่มีvoid*
และvoid*
อาร์กิวเมนต์สามารถเลียนแบบการเรียกใช้ฟังก์ชันสมาชิกไปยังวัตถุ ขออภัยฉันไม่รู้แหล่งข้อมูลที่เดินผ่าน "การออกแบบกลไกการติดต่อกลับ C 101"
this
ก็ยังคงเกี่ยวกับ นั่นคือสิ่งที่ฉันหมายถึง. ตกลงขอบคุณ
ใช้std::function
สำหรับเก็บวัตถุที่เรียกได้โดยไม่ตั้งใจ อนุญาตให้ผู้ใช้จัดเตรียมบริบทใด ๆ ก็ตามที่จำเป็นสำหรับการเรียกกลับ ตัวชี้ฟังก์ชั่นธรรมดาไม่ได้
หากคุณจำเป็นต้องใช้พอยน์เตอร์ฟังก์ชั่นธรรมดาด้วยเหตุผลบางอย่าง (อาจเป็นเพราะคุณต้องการ API ที่เข้ากันได้กับ C) ดังนั้นคุณควรเพิ่มvoid * user_context
อาร์กิวเมนต์เพื่อให้เป็นไปได้อย่างน้อยที่สุด (แม้ว่าจะไม่สะดวก) ฟังก์ชัน
เหตุผลเดียวที่ควรหลีกเลี่ยงstd::function
คือการสนับสนุนของคอมไพเลอร์ดั้งเดิมที่ขาดการสนับสนุนสำหรับเทมเพลตนี้ซึ่งได้รับการแนะนำใน C ++ 11
หากการสนับสนุนภาษา pre-C ++ 11 ไม่ใช่ข้อกำหนดการใช้std::function
จะช่วยให้ผู้โทรของคุณมีทางเลือกมากขึ้นในการใช้การติดต่อกลับทำให้เป็นตัวเลือกที่ดีกว่าเมื่อเทียบกับพอยน์เตอร์ฟังก์ชัน "ธรรมดา" มันเสนอทางเลือกให้แก่ผู้ใช้ API ของคุณมากขึ้นในขณะที่แยกแยะข้อมูลเฉพาะของการใช้งานสำหรับโค้ดของคุณที่ดำเนินการติดต่อกลับ
std::function
อาจนำรหัส VMT มาใช้ในบางกรณีซึ่งมีผลกระทบต่อประสิทธิภาพการทำงาน