ทรัพยากร C ++ เกือบทั้งหมดที่ฉันเคยเห็นที่พูดถึงเรื่องประเภทนี้บอกฉันว่าฉันควรใช้วิธีการหลายรูปแบบในการใช้ RTTI (การระบุประเภทรันไทม์) โดยทั่วไปแล้วฉันให้คำแนะนำแบบนี้อย่างจริงจังและจะพยายามทำความเข้าใจกับเหตุผล - ท้ายที่สุด C ++ เป็นสัตว์ร้ายที่ยิ่งใหญ่และยากที่จะเข้าใจในเชิงลึกทั้งหมด อย่างไรก็ตามสำหรับคำถามเฉพาะนี้ฉันกำลังวาดภาพว่างและต้องการดูว่าอินเทอร์เน็ตสามารถให้คำแนะนำประเภทใดได้บ้าง ก่อนอื่นให้ฉันสรุปสิ่งที่ฉันได้เรียนรู้จนถึงตอนนี้โดยระบุสาเหตุทั่วไปที่อ้างว่าทำไม RTTI จึง "ถือว่าเป็นอันตราย":
คอมไพเลอร์บางตัวไม่ใช้มัน / RTTI ไม่ได้เปิดใช้งานเสมอไป
ฉันไม่ซื้อข้อโต้แย้งนี้จริงๆ เหมือนกับการบอกว่าฉันไม่ควรใช้ฟีเจอร์ C ++ 14 เพราะมีคอมไพเลอร์ที่ไม่รองรับ แต่ก็ไม่มีใครกีดกันฉันจากการใช้คุณสมบัติ C ++ 14 โครงการส่วนใหญ่จะมีอิทธิพลเหนือคอมไพเลอร์ที่ใช้และวิธีกำหนดค่า แม้แต่การอ้างถึง manpage gcc:
-fno-rtti
ปิดใช้งานการสร้างข้อมูลเกี่ยวกับทุกคลาสด้วยฟังก์ชันเสมือนสำหรับใช้โดยคุณลักษณะการระบุชนิดรันไทม์ C ++ (dynamic_cast และ typeid) หากคุณไม่ได้ใช้ส่วนเหล่านั้นของภาษาคุณสามารถประหยัดพื้นที่ได้โดยใช้แฟล็กนี้ โปรดทราบว่าการจัดการข้อยกเว้นจะใช้ข้อมูลเดียวกัน แต่ G ++ สร้างขึ้นตามความจำเป็น ยังคงใช้ตัวดำเนินการ dynamic_cast สำหรับการแคสต์ที่ไม่ต้องการข้อมูลประเภทรันไทม์เช่นการร่ายเป็น "void *" หรือคลาสพื้นฐานที่ไม่คลุมเครือ
สิ่งนี้บอกฉันคือถ้าฉันไม่ได้ใช้ RTTI ฉันสามารถปิดการใช้งานได้ ก็เหมือนกับการบอกว่าถ้าคุณไม่ได้ใช้ Boost คุณไม่จำเป็นต้องเชื่อมโยงกับมัน -fno-rtti
ฉันไม่ได้มีการวางแผนสำหรับกรณีที่มีคนรวบรวมกับ นอกจากนี้คอมไพเลอร์จะล้มเหลวดังและชัดเจนในกรณีนี้
มีค่าใช้จ่ายหน่วยความจำเพิ่มเติม / อาจช้า
เมื่อใดก็ตามที่ฉันถูกล่อลวงให้ใช้ RTTI นั่นหมายความว่าฉันจำเป็นต้องเข้าถึงข้อมูลบางประเภทหรือลักษณะของชั้นเรียนของฉัน หากฉันใช้โซลูชันที่ไม่ใช้ RTTI โดยปกติหมายความว่าฉันจะต้องเพิ่มฟิลด์บางฟิลด์ในคลาสของฉันเพื่อจัดเก็บข้อมูลนี้ดังนั้นอาร์กิวเมนต์หน่วยความจำจึงเป็นโมฆะ (ฉันจะยกตัวอย่างต่อไปนี้)
dynamic_cast อาจช้าแน่นอน โดยปกติแล้วจะมีวิธีหลีกเลี่ยงการใช้สถานการณ์ที่มีความสำคัญต่อความเร็ว และฉันไม่ค่อยเห็นทางเลือกอื่น คำตอบนี้แนะนำให้ใช้ enum ซึ่งกำหนดไว้ในคลาสพื้นฐานเพื่อจัดเก็บชนิด จะใช้ได้ผลก็ต่อเมื่อคุณรู้จักคลาสที่ได้รับมาทั้งหมดของคุณโดยปริยาย มันค่อนข้างใหญ่ "ถ้า"!
จากคำตอบดังกล่าวดูเหมือนว่าค่าใช้จ่ายของ RTTI ยังไม่ชัดเจนเช่นกัน ต่างคนต่างวัดของต่างกัน
การออกแบบโพลีมอร์ฟิกที่หรูหราจะทำให้ RTTI ไม่จำเป็น
นี่เป็นคำแนะนำที่ฉันให้ความสำคัญอย่างจริงจัง ในกรณีนี้ฉันไม่สามารถหาโซลูชันที่ไม่ใช่ RTTI ที่ดีซึ่งครอบคลุมกรณีการใช้งาน RTTI ของฉันได้ ให้ฉันยกตัวอย่าง:
สมมติว่าฉันกำลังเขียนไลบรารีเพื่อจัดการกราฟของวัตถุบางประเภท ฉันต้องการอนุญาตให้ผู้ใช้สร้างประเภทของตนเองเมื่อใช้ไลบรารีของฉัน (ดังนั้นจึงไม่สามารถใช้วิธี enum ได้) ฉันมีคลาสพื้นฐานสำหรับโหนดของฉัน:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
ตอนนี้โหนดของฉันอาจเป็นประเภทต่างๆ แล้วสิ่งเหล่านี้:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
นรกทำไมไม่มีแม้แต่สิ่งเหล่านี้:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
ฟังก์ชันสุดท้ายน่าสนใจ นี่คือวิธีการเขียน:
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
ทั้งหมดนี้ดูเหมือนชัดเจนและสะอาด ฉันไม่จำเป็นต้องกำหนดแอตทริบิวต์หรือวิธีการที่ฉันไม่ต้องการมันคลาสโหนดฐานสามารถคงระดับและค่าเฉลี่ยได้ หากไม่มี RTTI ฉันจะเริ่มต้นที่ไหน บางทีฉันสามารถเพิ่มแอตทริบิวต์ node_type ให้กับคลาสพื้นฐาน:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
std :: string เป็นความคิดที่ดีสำหรับประเภทหรือไม่? อาจจะไม่ แต่ฉันจะใช้อะไรได้อีก? สร้างตัวเลขและหวังว่าจะไม่มีใครใช้มัน? นอกจากนี้ในกรณีของ orange_node ของฉันฉันจะทำอย่างไรถ้าฉันต้องการใช้วิธีการจาก red_node และ yellow_node ฉันจะต้องจัดเก็บหลายประเภทต่อโหนดหรือไม่? ดูเหมือนซับซ้อน
สรุป
ตัวอย่างนี้ดูไม่ซับซ้อนเกินไปหรือผิดปกติ (ฉันกำลังทำงานในสิ่งที่คล้ายกันในงานประจำวันของฉันโดยที่โหนดแสดงถึงฮาร์ดแวร์จริงที่ควบคุมผ่านซอฟต์แวร์และสิ่งที่ทำแตกต่างกันมากขึ้นอยู่กับสิ่งที่เป็น) แต่ฉันก็ไม่รู้วิธีที่สะอาดในการใช้เทมเพลตหรือวิธีการอื่น ๆ โปรดทราบว่าฉันกำลังพยายามเข้าใจปัญหาไม่ใช่ปกป้องตัวอย่างของฉัน การอ่านหน้าต่างๆของฉันเช่นคำตอบ SO ที่ฉันลิงก์ไว้ข้างบนและหน้านี้ใน Wikibooksดูเหมือนจะแนะนำว่าฉันใช้ RTTI ในทางที่ผิด แต่ฉันต้องการทราบสาเหตุ
ดังนั้นกลับไปที่คำถามเดิมของฉัน: เหตุใด 'ความหลากหลายที่บริสุทธิ์' จึงดีกว่าการใช้ RTTI?
node_base
เป็นส่วนหนึ่งของไลบรารีและผู้ใช้จะสร้างประเภทโหนดของตนเอง จากนั้นพวกเขาไม่สามารถแก้ไขnode_base
เพื่ออนุญาตโซลูชันอื่นได้ดังนั้น RTTI อาจกลายเป็นตัวเลือกที่ดีที่สุดในตอนนั้น ในทางกลับกันมีวิธีอื่น ๆ ในการออกแบบไลบรารีดังกล่าวเพื่อให้ประเภทโหนดใหม่สามารถใส่ได้อย่างหรูหรามากขึ้นโดยไม่จำเป็นต้องใช้ RTTI (และวิธีอื่น ๆ ในการออกแบบประเภทโหนดใหม่ด้วย)