จากมุมมองของการออกแบบเหตุใดใน C ++ จึงไม่มีคลาสพื้นฐานทั้งหมดobject
ในภาษาอื่น ๆ
typedef
เดียว ด้วยลำดับชั้นทั่วไปมากขึ้นคุณก็สามารถใช้หรือobject
iterator
จากมุมมองของการออกแบบเหตุใดใน C ++ จึงไม่มีคลาสพื้นฐานทั้งหมดobject
ในภาษาอื่น ๆ
typedef
เดียว ด้วยลำดับชั้นทั่วไปมากขึ้นคุณก็สามารถใช้หรือobject
iterator
คำตอบ:
การพิจารณาคดีที่ชัดเจนจะพบในคำถามที่พบบ่อยของ Stroustrup ในระยะสั้นมันไม่ได้สื่อความหมายใด ๆ มันจะมีค่าใช้จ่าย เทมเพลตมีประโยชน์มากกว่าสำหรับคอนเทนเนอร์
เหตุใด C ++ จึงไม่มีวัตถุคลาสสากล
เราไม่ต้องการอย่างใดอย่างหนึ่ง: การเขียนโปรแกรมทั่วไปมีทางเลือกที่ปลอดภัยในการพิมพ์แบบคงที่ในกรณีส่วนใหญ่ กรณีอื่น ๆ ได้รับการจัดการโดยใช้การสืบทอดหลายรายการ
ไม่มีคลาสสากลที่มีประโยชน์: สากลที่แท้จริงไม่มีความหมายเป็นของตัวเอง
คลาส "สากล" กระตุ้นการคิดเกี่ยวกับประเภทและอินเทอร์เฟซที่ไม่เป็นระเบียบและนำไปสู่การตรวจสอบรันไทม์มากเกินไป
การใช้คลาสฐานสากลหมายถึงต้นทุน: อ็อบเจ็กต์ต้องได้รับการจัดสรรฮีปให้เป็นโพลีมอร์ฟิก ซึ่งหมายถึงหน่วยความจำและต้นทุนการเข้าถึง วัตถุฮีปไม่สนับสนุนความหมายของการคัดลอกโดยธรรมชาติ วัตถุฮีปไม่สนับสนุนพฤติกรรมที่กำหนดขอบเขตง่ายๆ (ซึ่งทำให้การจัดการทรัพยากรยุ่งยาก) คลาสฐานสากลสนับสนุนการใช้ dynamic_cast และการตรวจสอบรันไทม์อื่น ๆ
Objects must be heap-allocated to be polymorphic
- ฉันไม่คิดว่าคำพูดนี้ถูกต้องโดยทั่วไป คุณสามารถสร้างอินสแตนซ์ของคลาสโพลีมอร์ฟิคบนสแต็กได้อย่างแน่นอนและส่งต่อเป็นตัวชี้ไปยังคลาสพื้นฐานตัวใดตัวหนึ่งซึ่งส่งผลให้เกิดพฤติกรรมที่หลากหลาย
Foo
เป็นอ้างอิง-to- Bar
เมื่อBar
ไม่สืบทอดFoo
, deterministicallyพ่นยกเว้น ในภาษา C ++ การพยายามร่ายพอยน์เตอร์ทู - ฟูลงในพอยน์เตอร์ทูบาร์สามารถส่งหุ่นยนต์ย้อนเวลากลับไปฆ่าซาราห์คอนเนอร์ได้
ก่อนอื่นให้คิดว่าทำไมคุณถึงต้องการมีคลาสพื้นฐานตั้งแต่แรก ฉันนึกเหตุผลได้หลายประการ:
นี่คือเหตุผลที่ดีสองประการที่ภาษาของแบรนด์ Smalltalk, Ruby และ Objective-C มีคลาสพื้นฐาน (ในทางเทคนิค Objective-C ไม่มีคลาสฐาน แต่สำหรับเจตนาและวัตถุประสงค์ทั้งหมด)
สำหรับ # 1 ความต้องการคลาสพื้นฐานที่รวมอ็อบเจ็กต์ทั้งหมดไว้ในอินเทอร์เฟซเดียวนั้นถูกขัดขวางโดยการรวมเทมเพลตใน C ++ ตัวอย่างเช่น:
void somethingGeneric(Base);
Derived object;
somethingGeneric(object);
เป็นสิ่งที่ไม่จำเป็นเมื่อคุณสามารถรักษาความสมบูรณ์ของประเภทได้ตลอดทางโดยใช้ความหลากหลายของพาราเมตริก
template <class T>
void somethingGeneric(T);
Derived object;
somethingGeneric(object);
สำหรับ # 2 ในขณะที่ Objective-C ขั้นตอนการจัดการหน่วยความจำเป็นส่วนหนึ่งของการนำไปใช้งานของคลาสและสืบทอดมาจากคลาสฐานการจัดการหน่วยความจำใน C ++ จะดำเนินการโดยใช้องค์ประกอบแทนที่จะสืบทอด ตัวอย่างเช่นคุณสามารถกำหนดสมาร์ทพอยน์เตอร์แรปเปอร์ซึ่งจะทำการอ้างอิงการนับอ็อบเจ็กต์ประเภทใดก็ได้:
template <class T>
struct refcounted
{
refcounted(T* object) : _object(object), _count(0) {}
T* operator->() { return _object; }
operator T*() { return _object; }
void retain() { ++_count; }
void release()
{
if (--_count == 0) { delete _object; }
}
private:
T* _object;
int _count;
};
จากนั้นแทนที่จะเรียกใช้เมธอดบนอ็อบเจ็กต์เองคุณจะเรียกใช้เมธอดใน wrapper สิ่งนี้ไม่เพียง แต่ช่วยให้สามารถเขียนโปรแกรมทั่วไปได้มากขึ้นเท่านั้น แต่ยังช่วยให้คุณสามารถแยกข้อกังวลต่างๆได้อีกด้วย (เนื่องจากตามหลักการแล้ววัตถุของคุณควรกังวลเกี่ยวกับสิ่งที่ควรทำมากกว่าวิธีจัดการหน่วยความจำในสถานการณ์ต่างๆ)
สุดท้ายในภาษาที่มีทั้งแบบดั้งเดิมและวัตถุจริงเช่น C ++ ประโยชน์ของการมีคลาสพื้นฐาน (อินเทอร์เฟซที่สอดคล้องกันสำหรับทุกๆค่า) จะหายไปตั้งแต่นั้นมาคุณมีค่าบางอย่างที่ไม่สามารถสอดคล้องกับอินเทอร์เฟซนั้นได้ ในการใช้ primitives ในสถานการณ์เช่นนั้นคุณต้องยกพวกมันเป็นวัตถุ (ถ้าคอมไพเลอร์ของคุณไม่ทำโดยอัตโนมัติ) สิ่งนี้สร้างความยุ่งยากมากมาย
ดังนั้นคำตอบสั้น ๆ สำหรับคำถามของคุณ: C ++ ไม่มีคลาสพื้นฐานเนื่องจากมีความหลากหลายเชิงพาราเมตริกผ่านเทมเพลตจึงไม่จำเป็นต้องมี
object
( System.Object
) ได้ แต่ไม่จำเป็นต้องเป็น ไปยังคอมไพเลอร์int
และSystem.Int32
เป็นนามแฝงและสามารถใช้แทนกันได้ รันไทม์จะจัดการกับการชกมวยเมื่อจำเป็น
std::shared_ptr
ที่ควรใช้แทน
กระบวนทัศน์ที่โดดเด่นสำหรับตัวแปร C ++ คือ pass-by-value ไม่ใช่ pass-by-ref การบังคับให้ทุกอย่างมาจากรูทObject
จะทำให้การส่งผ่านค่าเหล่านั้นเป็นข้อผิดพลาด ipse facto
(เนื่องจากการยอมรับ Object ด้วยค่าเป็นพารามิเตอร์โดยคำจำกัดความจะหั่นมันและลบวิญญาณของมันออก)
สิ่งนี้ไม่เป็นที่พอใจ C ++ ทำให้คุณคิดว่าคุณต้องการค่าหรือความหมายอ้างอิงให้คุณเลือก นี่เป็นเรื่องใหญ่ในการคำนวณประสิทธิภาพ
Object
จะค่อนข้างน้อย
ปัญหาคือมีประเภทนี้ใน C ++! มันคือvoid
. :-) ตัวชี้ใด ๆ อาจถูกส่งโดยปริยายอย่างปลอดภัยvoid *
รวมถึงตัวชี้ไปยังประเภทพื้นฐานคลาสที่ไม่มีตารางเสมือนและคลาสที่มีตารางเสมือน
เนื่องจากควรเข้ากันได้กับทุกประเภทของอ็อบเจ็กต์void
จึงไม่มีเมธอดเสมือนจริง หากไม่มีฟังก์ชันเสมือนและ RTTI จะไม่สามารถรับข้อมูลที่เป็นประโยชน์เกี่ยวกับประเภทได้void
(ตรงกับทุกประเภทดังนั้นจึงสามารถบอกได้เฉพาะสิ่งที่เป็นจริงสำหรับทุกประเภท) แต่ฟังก์ชันเสมือนและ RTTI จะทำให้ประเภทที่เรียบง่ายไม่มีประสิทธิภาพมากและหยุด C ++ จากการเป็น ภาษาที่เหมาะสำหรับการเขียนโปรแกรมระดับต่ำที่มีการเข้าถึงหน่วยความจำโดยตรงเป็นต้น
ดังนั้นจึงมีประเภทดังกล่าว มันให้อินเทอร์เฟซที่เรียบง่ายมาก (ในความเป็นจริงว่างเปล่า) เนื่องจากลักษณะของภาษาระดับต่ำ :-)
void
คุณไม่สามารถมีวัตถุของการพิมพ์
void
มันจะไม่คอมไพล์และคุณได้รับข้อผิดพลาดนี้ ใน Java คุณสามารถมีตัวแปรประเภทObject
และมันจะทำงานได้ นั่นคือความแตกต่างระหว่างvoid
และประเภท "จริง" บางทีอาจเป็นความจริงที่void
เป็นประเภทพื้นฐานสำหรับทุกสิ่ง แต่เนื่องจากไม่มีตัวสร้างวิธีการหรือฟิลด์ใด ๆ จึงไม่มีทางที่จะบอกได้ว่ามีหรือไม่ การยืนยันนี้ไม่สามารถพิสูจน์หรือพิสูจน์ไม่ได้
void
เป็นประเภท (และค้นพบการใช้งานที่? :
ค่อนข้างฉลาด) แต่ก็ยังห่างไกลจากการเป็นคลาสฐานสากล ประการหนึ่งก็คือ "ประเภทที่ไม่สมบูรณ์ซึ่งไม่สามารถทำให้เสร็จสมบูรณ์ได้" และสำหรับอีกประเภทหนึ่งคือไม่มีvoid&
ประเภทใด
C ++ เป็นภาษาที่พิมพ์อย่างรุนแรง แต่เป็นที่น่างงว่าไม่มีประเภทออบเจ็กต์สากลในบริบทของความเชี่ยวชาญพิเศษของเทมเพลต
ยกตัวอย่างเช่นรูปแบบ
template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
ซึ่งสามารถสร้างอินสแตนซ์เป็น
Hook<decltype(some_function)> ...;
ตอนนี้สมมติว่าเราต้องการเหมือนกันสำหรับฟังก์ชันเฉพาะ ชอบ
template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
ด้วยการสร้างอินสแตนซ์พิเศษ
Hook<some_function> ...
แต่อนิจจาแม้ว่าคลาส T สามารถยืนอยู่ในประเภทใดก็ได้ (คลาสหรือไม่ก็ได้) ก่อนที่จะเชี่ยวชาญ แต่ก็ไม่มีสิ่งที่เทียบเท่ากันauto fallback
(ฉันกำลังใช้ไวยากรณ์นั้นเป็นรูปแบบทั่วไปที่ไม่ใช่ประเภทที่ชัดเจนที่สุดในบริบทนี้) ที่สามารถยืนหยัดได้ อาร์กิวเมนต์แม่แบบที่ไม่ใช่ประเภทก่อนความเชี่ยวชาญ
ดังนั้นโดยทั่วไปรูปแบบนี้จะไม่ถ่ายโอนจากอาร์กิวเมนต์เทมเพลตประเภทไปยังอาร์กิวเมนต์แม่แบบที่ไม่ใช่ประเภท
เช่นเดียวกับหลาย ๆ มุมในภาษา C ++ คำตอบน่าจะเป็น "ไม่มีกรรมการคนใดคิด"
ReturnType fallback(ArgTypes...)
จะเป็นการออกแบบที่ไม่ดี template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...
ทำในสิ่งที่คุณต้องการและหมายความว่าพารามิเตอร์เทมเพลตHook
เป็นชนิดที่
C ++ ในตอนแรกเรียกว่า "C พร้อมคลาส" เป็นความก้าวหน้าของภาษา C ซึ่งแตกต่างจากสิ่งที่ทันสมัยกว่าเช่น C # และคุณไม่สามารถเห็น C ++ เป็นภาษา แต่เป็นพื้นฐานของภาษา (ใช่ฉันจำหนังสือ Scott Meyers C ++ ที่มีประสิทธิภาพ)
C นั้นเป็นการผสมผสานระหว่างภาษาภาษาโปรแกรม C และตัวประมวลผลล่วงหน้า
C ++ เพิ่มส่วนผสมอื่น:
คลาส / วัตถุเข้าใกล้
เทมเพลต
STL
โดยส่วนตัวแล้วฉันไม่ชอบบางสิ่งที่มาจาก C ถึง C ++ โดยตรง ตัวอย่างหนึ่งคือคุณลักษณะ enum วิธีที่ C # อนุญาตให้นักพัฒนาใช้เป็นวิธีที่ดีกว่า: จำกัด enum ในขอบเขตของตัวเองมีคุณสมบัติ Count และสามารถทำซ้ำได้อย่างง่ายดาย
เนื่องจาก C ++ ต้องการให้เข้ากันได้กับ C แบบย้อนยุคผู้ออกแบบจึงอนุญาตให้ภาษา C เข้าสู่ C ++ ได้ทั้งหมด (มีความแตกต่างเล็กน้อย แต่ฉันจำสิ่งใด ๆ ที่คุณสามารถทำได้โดยใช้คอมไพเลอร์ C ที่คุณ ไม่สามารถทำได้โดยใช้คอมไพเลอร์ C ++)