ค่าใช้จ่ายในการใช้ destructors เสมือนจริงคืออะไรหากฉันใช้แม้ว่าจะไม่จำเป็นก็ตาม
ค่าใช้จ่ายในการแนะนำฟังก์ชั่นเสมือนใด ๆให้กับคลาส (สืบทอดมาหรือเป็นส่วนหนึ่งของคำจำกัดความของคลาส) อาจเป็นค่าใช้จ่ายเริ่มต้นที่สูงชัน (หรือไม่ขึ้นอยู่กับวัตถุ) ของตัวชี้เสมือนที่เก็บต่อวัตถุเช่น:
struct Integer
{
virtual ~Integer() {}
int value;
};
ในกรณีนี้ราคาหน่วยความจำค่อนข้างใหญ่ ขนาดหน่วยความจำจริงของอินสแตนซ์ของชั้นเรียนมักจะมีลักษณะเช่นนี้ในสถาปัตยกรรม 64 บิต:
struct Integer
{
// 8 byte vptr overhead
int value; // 4 bytes
// typically 4 more bytes of padding for alignment of vptr
};
ผลรวมคือ 16 ไบต์สำหรับInteger
คลาสนี้ซึ่งตรงข้ามกับเพียง 4 ไบต์ หากเราเก็บหนึ่งล้านเหล่านี้ไว้ในอาเรย์เราจะจบลงด้วยการใช้หน่วยความจำ 16 เมกะไบต์: ขนาดแคชซีพียู L3 8 เมกะไบต์สองเท่าและการวนซ้ำในอาเรย์ซ้ำหลายครั้งอาจช้ากว่า 4 เมกะไบต์เทียบเท่า โดยไม่มีตัวชี้เสมือนอันเป็นผลมาจากการเพิ่มแคชและความผิดพลาดของหน้า
อย่างไรก็ตามราคาตัวชี้เสมือนต่อวัตถุนั้นไม่เพิ่มขึ้นเมื่อใช้ฟังก์ชันเสมือนจริงมากขึ้น คุณสามารถมีฟังก์ชันสมาชิกเสมือนได้ 100 ฟังก์ชันในคลาสและค่าใช้จ่ายต่ออินสแตนซ์จะยังคงเป็นตัวชี้เสมือนเดียว
โดยทั่วไปแล้วตัวชี้เสมือนจะเป็นข้อกังวลที่เกิดขึ้นทันทีจากมุมมองด้านบน อย่างไรก็ตามนอกเหนือจากตัวชี้เสมือนต่ออินสแตนซ์แล้วยังมีค่าใช้จ่ายต่อคลาส แต่ละคลาสที่มีฟังก์ชั่นเสมือนสร้างvtable
หน่วยความจำในที่เก็บที่อยู่ไปยังฟังก์ชั่นที่มันควรจะเรียกจริง (เสมือน / การจัดส่งแบบไดนามิก) เมื่อมีการเรียกฟังก์ชั่นเสมือน ที่vptr
เก็บต่ออินสแตนซ์นั้นชี้ไปที่คลาสเฉพาะvtable
นี้ ค่าใช้จ่ายนี้มักจะเป็นความกังวลน้อยลง แต่มันอาจขยายขนาดไบนารีของคุณและเพิ่มค่าใช้จ่ายเล็กน้อยหากค่าใช้จ่ายนี้จ่ายโดยไม่จำเป็นสำหรับพันคลาสในโค๊ดเบสที่ซับซ้อนเช่นvtable
ค่าใช้จ่ายจริงเพิ่มขึ้นตามสัดส่วน ฟังก์ชั่นเสมือนจริงมากขึ้นในการผสม
นักพัฒนา Java ที่ทำงานในพื้นที่ที่มีประสิทธิภาพสูงเข้าใจค่าใช้จ่ายประเภทนี้ค่อนข้างดี (แม้ว่ามักจะอธิบายไว้ในบริบทของการชกมวย) เนื่องจากผู้ใช้ที่กำหนดโดยจาวาประเภท Java ที่สืบทอดโดยปริยายจากobject
คลาสฐานกลางและฟังก์ชั่นทั้งหมดใน Java ) ในลักษณะเว้นแต่จะระบุไว้เป็นอย่างอื่น ดังนั้น Java Integer
จึงมีแนวโน้มที่จะต้องการหน่วยความจำ 16 ไบต์บนแพลตฟอร์ม 64 บิตเนื่องจากvptr
เมตาดาต้าสไตล์นี้เชื่อมโยงกับอินสแตนซ์และโดยทั่วไปแล้วมันเป็นไปไม่ได้ที่จาวาจะห่อสิ่งใดสิ่งหนึ่งint
ลงในชั้นเรียน ต้นทุนประสิทธิภาพสำหรับมัน
ถ้าอย่างนั้นคำถามคือทำไม c ++ ไม่ได้ตั้งค่า destructors ทั้งหมดโดยค่าเริ่มต้น?
C ++ ชอบประสิทธิภาพการทำงานด้วยความคิดแบบ "จ่ายเท่าที่คุณต้องการ" และยังมีการออกแบบฮาร์ดแวร์ที่ขับเคลื่อนด้วยโลหะเปลือยจำนวนมากที่สืบทอดมาจาก C. มันไม่ต้องการรวมค่าใช้จ่ายที่ไม่จำเป็นสำหรับการสร้าง vtable และการจัดส่งแบบไดนามิกสำหรับ ทุกคลาส / อินสแตนซ์ที่เกี่ยวข้อง หากประสิทธิภาพไม่ใช่หนึ่งในเหตุผลสำคัญที่คุณใช้ภาษาเช่น C ++ คุณอาจได้รับประโยชน์เพิ่มเติมจากภาษาการเขียนโปรแกรมอื่น ๆ เนื่องจากภาษา C ++ จำนวนมากนั้นมีความปลอดภัยน้อยกว่าและยากกว่าที่ควรจะเป็นเพราะประสิทธิภาพมักจะเป็น เหตุผลสำคัญในการสนับสนุนการออกแบบดังกล่าว
เมื่อใดที่ฉันไม่ต้องการใช้ destructors เสมือน?
ค่อนข้างบ่อย. ถ้าคลาสไม่ได้ถูกออกแบบมาให้สืบทอดมันก็ไม่จำเป็นต้องมี destructor เสมือนจริงและจะจบลงด้วยการจ่ายค่าโสหุ้ยขนาดใหญ่สำหรับสิ่งที่ไม่ต้องการ ในทำนองเดียวกันแม้ว่าคลาสจะได้รับการออกแบบให้สืบทอด แต่คุณไม่เคยลบอินสแตนซ์ย่อยผ่านตัวชี้พื้นฐานดังนั้นมันจึงไม่จำเป็นต้องมี destructor เสมือน ในกรณีดังกล่าวการปฏิบัติที่ปลอดภัยคือการกำหนด destructor ที่ไม่ใช่เสมือนที่ได้รับการป้องกันเช่น:
class BaseClass
{
protected:
// Disallow deleting/destroying subclass objects through `BaseClass*`.
~BaseClass() {}
};
ในกรณีใดฉันไม่ควรใช้ destructors เสมือน?
จริง ๆ แล้วมันง่ายกว่าที่จะครอบคลุมเมื่อคุณควรใช้ destructors เสมือน บ่อยครั้งที่คลาสที่มากขึ้นใน codebase ของคุณจะไม่ได้รับการออกแบบสำหรับการสืบทอด
std::vector
ตัวอย่างเช่นไม่ได้ถูกออกแบบมาให้สืบทอดและโดยทั่วไปไม่ควรสืบทอด (การออกแบบที่สั่นคลอนมาก) เนื่องจากจะทำให้เกิดปัญหาการลบตัวชี้ฐานนี้ ( std::vector
จงใจหลีกเลี่ยงการทำลายเสมือนจริง) โดยเจตนานอกเหนือจากปัญหาการแบ่งวัตถุที่เงอะงะคลาสที่ได้รับจะเพิ่มสถานะใหม่ใด ๆ
โดยทั่วไปคลาสที่สืบทอดควรมี destructor เสมือนสาธารณะหรือที่ได้รับการป้องกัน จากC++ Coding Standards
ตอนที่ 50:
50. ทำให้ตัวทำลายคลาสพื้นฐานเป็นสาธารณะและเสมือนหรือได้รับการป้องกันและไม่ใช่เสมือน หากต้องการลบหรือไม่ลบ; นั่นคือคำถาม: หากการลบผ่านตัวชี้ไปยังฐานฐานควรได้รับอนุญาตจากนั้น destructor ของฐานจะต้องเป็นสาธารณะและเสมือน มิฉะนั้นควรได้รับการปกป้องและไม่เป็นเสมือน
หนึ่งในสิ่งที่ C ++ มีแนวโน้มที่จะเน้นโดยปริยาย (เนื่องจากการออกแบบมีแนวโน้มที่จะเปราะบางและไม่แน่นอนและอาจไม่ปลอดภัย) คือความคิดที่ว่าการสืบทอดไม่ใช่กลไกที่ถูกออกแบบมาให้ใช้ภายหลัง มันเป็นกลไกในการเพิ่มความสามารถโดยมี polymorphism อยู่ในใจ แต่สิ่งหนึ่งที่ต้องการการมองการณ์ไกลเป็นสิ่งจำเป็นสำหรับการขยายตัว เป็นผลให้คลาสพื้นฐานของคุณควรได้รับการออกแบบเป็นรากของลำดับชั้นการสืบทอดล่วงหน้าและไม่ใช่สิ่งที่คุณสืบทอดมาในภายหลังในภายหลังโดยไม่ต้องมีการคาดการณ์ล่วงหน้า
ในกรณีที่คุณต้องการรับมรดกเพื่อนำโค้ดที่มีอยู่กลับมาใช้บ่อยครั้งการส่งเสริมการแต่งเพลงก็มักจะได้รับการสนับสนุนอย่างมาก (Composite Reuse Principle)