ความหลากหลายใน C ++


129

AFAIK:

C ++ มีความแตกต่างกันสามประเภท

  • ฟังก์ชันเสมือนจริง
  • ชื่อฟังก์ชันมากเกินไป
  • ตัวดำเนินการมากเกินไป

นอกเหนือจากความหลากหลายสามประเภทข้างต้นแล้วยังมีความหลากหลายประเภทอื่น ๆ อีกด้วย:

  • เวลาทำงาน
  • เวลารวบรวม
  • ความแตกต่างแบบเฉพาะกิจ
  • พหุนามพาราเมตริก

ฉันรู้ว่าความหลากหลายของรันไทม์สามารถทำได้โดยฟังก์ชันเสมือนจริง และความแตกต่างแบบคงที่สามารถทำได้โดยฟังก์ชันเทมเพลต

แต่สำหรับอีกสองคน

โพลีมอร์ฟิซึมเฉพาะกิจ:

หากช่วงของประเภทจริงที่สามารถใช้ได้มี จำกัด และต้องระบุชุดค่าผสมทีละรายการก่อนใช้งานสิ่งนี้เรียกว่า ad-hoc polymorphism

ความหลากหลายเชิงพาราเมตริก:

หากเขียนโค้ดทั้งหมดโดยไม่ระบุประเภทใดประเภทหนึ่งดังนั้นจึงสามารถใช้งานได้อย่างโปร่งใสกับประเภทใหม่ ๆ จำนวนมากเรียกว่าพหุนามพาราเมตริก

ฉันแทบไม่เข้าใจพวกเขาเลย :(

ใครช่วยอธิบายทั้งสองอย่างด้วยตัวอย่างได้ไหม ฉันหวังว่าคำตอบสำหรับคำถามนี้จะเป็นประโยชน์สำหรับผู้ผ่านการคัดเลือกใหม่จากวิทยาลัยของพวกเขา


30
จริงๆแล้ว C ++ มีความหลากหลายสี่ประเภท: พาราเมตริก (ทั่วไปผ่านเทมเพลตใน C ++) การรวม (การพิมพ์ย่อยผ่านวิธีเสมือนใน C ++) การโอเวอร์โหลดและการบีบบังคับ (การแปลงโดยนัย) โดยทั่วไปแล้วมีความแตกต่างเพียงเล็กน้อยระหว่างการโอเวอร์โหลดของฟังก์ชันและการโอเวอร์โหลดของตัวดำเนินการ
fredoverflow

ดูเหมือนว่าเว็บไซต์ที่ฉันกล่าวถึงจะทำให้หลายคนเข้าใจผิด .. ฉันถูกต้องหรือไม่?
Vijay

@zombie: เว็บไซต์นั้นมีแนวคิดที่ดีมากมาย แต่ไม่แม่นยำและสอดคล้องในการใช้คำศัพท์ (ตัวอย่างเช่นเมื่อเริ่มพูดถึงความหลากหลายของการจัดส่งเสมือน / รันไทม์มันจะมีข้อความจำนวนมากเกี่ยวกับความหลากหลายที่ไม่ถูกต้อง โดยทั่วไป แต่เป็นจริงสำหรับการจัดส่งเสมือน) หากคุณเข้าใจเรื่องนี้แล้วคุณสามารถเชื่อมโยงกับสิ่งที่กำลังพูดและแทรกคำเตือนที่จำเป็นทางจิตใจได้ แต่ก็ยากที่จะไปถึงที่นั่นโดยการอ่านเว็บไซต์ ....
Tony Delroy

คำศัพท์บางคำเป็นคำที่ใกล้เคียงกันหรือเกี่ยวข้องมากกว่า แต่มีข้อ จำกัด มากกว่าคำอื่น ๆ ตัวอย่างเช่นคำว่า "ad-hoc polymorphism" ส่วนใหญ่จะใช้ใน Haskell จากประสบการณ์ของฉัน แต่ "ฟังก์ชันเสมือน" มีความเกี่ยวข้องอย่างใกล้ชิด ความแตกต่างเล็กน้อยคือ "ฟังก์ชันเสมือน" เป็นคำเชิงวัตถุที่อ้างถึงฟังก์ชันสมาชิกที่มี "การเชื่อมต่อล่าช้า" "การจัดส่งหลายรายการ" ยังเป็นความหลากหลายแบบเฉพาะกิจ และตามที่ FredOverflow กล่าวไว้ทั้งตัวดำเนินการและการทำงานมากเกินไปโดยพื้นฐานแล้วเป็นสิ่งเดียวกัน
Steve314

ฉันแก้ไขการจัดรูปแบบให้คุณแล้ว โปรดอ่านวิธีใช้ทางด้านขวาของบานหน้าต่างแก้ไข ผู้ที่มีคำถามมากกว่า 200 ข้อและ> 3k ควรรู้เรื่องพื้นฐานนี้ นอกจากนี้คุณอาจต้องการซื้อแป้นพิมพ์ใหม่ ปุ่ม Shift ของคนนี้ดูเหมือนจะล้มเหลวระหว่างกัน อ้อและ: ไม่มี "ฟังก์ชันเทมเพลต"ใน C ++ มี แต่มีแม่แบบฟังก์ชั่น
sbi

คำตอบ:


219

ความเข้าใจเกี่ยวกับ / ข้อกำหนดสำหรับความหลากหลาย

เพื่อทำความเข้าใจเกี่ยวกับความหลากหลาย - เป็นคำที่ใช้ในวิทยาการคอมพิวเตอร์ - ช่วยให้เริ่มต้นจากการทดสอบและนิยามของคำศัพท์ง่ายๆ พิจารณา:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

นี่f()คือการดำเนินการบางอย่างและได้รับค่าxและyเป็นอินพุต

ในการแสดงความหลากหลายf()ต้องสามารถทำงานกับค่าที่แตกต่างกันอย่างน้อยสองประเภท (เช่นintและdouble) ค้นหาและเรียกใช้รหัสที่เหมาะสมกับประเภทที่แตกต่างกัน


กลไก C ++ สำหรับความหลากหลาย

ความหลากหลายที่ระบุโดยโปรแกรมเมอร์อย่างชัดเจน

คุณสามารถเขียนf()เพื่อให้สามารถใช้งานได้หลายประเภทด้วยวิธีใด ๆ ต่อไปนี้:

  • preprocessing:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
  • การบรรทุกเกินพิกัด:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
  • แม่แบบ:

    template <typename T>
    void f(T& x) { x += 2; }
  • การจัดส่งเสมือน:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch

กลไกอื่น ๆ ที่เกี่ยวข้อง

ความหลากหลายของคอมไพเลอร์ที่จัดเตรียมไว้สำหรับประเภทบิวด์อินการแปลงมาตรฐานและการหล่อ / การบีบบังคับจะกล่าวถึงในภายหลังเพื่อความสมบูรณ์ดังนี้:

  • พวกเขามักจะเข้าใจโดยสัญชาตญาณอยู่แล้ว (รับประกันว่า " โอ้นั่นปฏิกิริยา ")
  • พวกเขาส่งผลกระทบต่อเกณฑ์ในการกำหนดและความราบรื่นในการใช้กลไกข้างต้นและ
  • คำอธิบายเป็นสิ่งที่ทำให้ไขว้เขวจากแนวคิดที่สำคัญกว่า

คำศัพท์

การจัดหมวดหมู่เพิ่มเติม

ด้วยกลไกหลายรูปแบบด้านบนเราสามารถจัดหมวดหมู่ได้หลายวิธี:

  • เมื่อใดที่มีการเลือกรหัสเฉพาะประเภท polymorphic

    • เวลาทำงานหมายความว่าคอมไพเลอร์จะต้องสร้างรหัสสำหรับทุกประเภทโปรแกรมอาจจัดการในขณะที่ทำงานและที่ใช้เวลารหัสที่ถูกต้องจะถูกเลือก ( ส่งเสมือน )
    • เวลาคอมไพล์หมายถึงตัวเลือกของรหัสเฉพาะประเภทที่เกิดขึ้นระหว่างการคอมไพล์ ผลที่ตามมา: พูดว่าโปรแกรมที่เรียกfข้างต้นโดยมีintอาร์กิวเมนต์เท่านั้น - ขึ้นอยู่กับกลไกความหลากหลายที่ใช้และตัวเลือกการแทรกที่คอมไพเลอร์อาจหลีกเลี่ยงการสร้างโค้ดใด ๆf(double)หรือโค้ดที่สร้างขึ้นอาจถูกโยนทิ้งไปในบางจุดในการคอมไพล์หรือการเชื่อมโยง ( กลไกทั้งหมดข้างต้นยกเว้นการจัดส่งเสมือน )

  • รองรับประเภทใดบ้าง

    • Ad-hocหมายถึงคุณระบุรหัสที่ชัดเจนเพื่อรองรับแต่ละประเภท (เช่นการโอเวอร์โหลดเทมเพลตความเชี่ยวชาญพิเศษ) คุณเพิ่มการสนับสนุน "สำหรับสิ่งนี้" อย่างชัดเจน (ตามความหมายของเฉพาะกิจ ) ประเภทอื่น "สิ่งนี้" และอาจเป็น "สิ่งนั้น" ด้วย ;-)
    • ความหมายพาราเมตริกคุณสามารถลองใช้ฟังก์ชันสำหรับประเภทพารามิเตอร์ต่างๆโดยไม่ต้องดำเนินการใด ๆ โดยเฉพาะเพื่อให้สามารถรองรับได้ (เช่นเทมเพลตมาโคร) ออบเจ็กต์ที่มีฟังก์ชัน / ตัวดำเนินการที่ทำหน้าที่เหมือนเทมเพลต / มาโครคาดว่า1 คือเทมเพลต / มาโครทั้งหมดที่ต้องใช้ในการทำงานโดยประเภทที่แน่นอนไม่เกี่ยวข้อง ว่า "แนวคิด" นำโดย C ++ 20 แสดงความคาดหวังและการบังคับใช้ดังกล่าว - ดูcppreferenceหน้านี่

      • ความหลากหลายเชิงพาราเมตริกให้การพิมพ์แบบเป็ดซึ่งเป็นแนวคิดที่มาจาก James Whitcomb Riley ซึ่งเห็นได้ชัดว่า"เมื่อฉันเห็นนกที่เดินเหมือนเป็ดและว่ายน้ำเหมือนเป็ดและหลอกลวงเหมือนเป็ดฉันเรียกนกตัวนั้นว่าเป็ด" .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
    • ชนิดย่อย (หรือที่เรียกว่าการรวม) ความหลากหลายช่วยให้คุณสามารถทำงานกับประเภทใหม่ได้โดยไม่ต้องอัปเดตอัลกอริทึม / ฟังก์ชัน แต่ต้องได้มาจากคลาสฐานเดียวกัน (การจัดส่งเสมือน)

1 - เทมเพลตมีความยืดหยุ่นสูง SFINAE (ดูเพิ่มเติมstd::enable_if) ช่วยให้มีความคาดหวังหลายชุดสำหรับความหลากหลายของพาราเมตริก ตัวอย่างเช่นคุณอาจเข้ารหัสว่าเมื่อประเภทของข้อมูลที่คุณกำลังประมวลผลมี.size()สมาชิกคุณจะใช้ฟังก์ชันหนึ่งหรือฟังก์ชันอื่นที่ไม่ต้องการ.size()(แต่น่าจะมีปัญหาไม่ทางใดก็ทางหนึ่งเช่นใช้การstrlen()พิมพ์ที่ช้าลงหรือไม่พิมพ์เป็น ข้อความที่เป็นประโยชน์ในบันทึก) คุณยังสามารถระบุลักษณะการทำงานเฉพาะกิจเมื่อเทมเพลตถูกสร้างอินสแตนซ์ด้วยพารามิเตอร์ที่เฉพาะเจาะจงไม่ว่าจะปล่อยพารามิเตอร์บางพารามิเตอร์ ( ความเชี่ยวชาญพิเศษของเทมเพลตบางส่วน ) หรือไม่ ( ความเชี่ยวชาญทั้งหมด )

"Polymorphic"

Alf Steinbach ให้ความเห็นว่าใน C ++ Standard polymorphicอ้างถึงความหลากหลายของเวลาทำงานโดยใช้ virtual dispatch เท่านั้น คอมพ์ทั่วไป วิทย์ ความหมายรวมมากขึ้นตามอภิธานศัพท์ของผู้สร้าง C ++ Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

ความหลากหลาย - ให้อินเทอร์เฟซเดียวสำหรับเอนทิตีประเภทต่างๆ ฟังก์ชันเสมือนจัดเตรียมความหลากหลายแบบไดนามิก (รันไทม์) ผ่านอินเทอร์เฟซที่จัดเตรียมโดยคลาสฐาน ฟังก์ชันและเทมเพลตที่มากเกินไปให้ความหลากหลายแบบคงที่ (เวลาคอมไพล์) TC ++ PL 12.2.6, 13.6.1, D&E 2.9

คำตอบนี้ - เช่นเดียวกับคำถาม - เกี่ยวข้องกับคุณสมบัติ C ++ กับ Comp วิทย์ คำศัพท์

อภิปรายผล

ด้วยมาตรฐาน C ++ ใช้คำจำกัดความของ "ความหลากหลาย" ที่แคบกว่าคอมพ์ วิทย์ ชุมชนเพื่อให้เกิดความเข้าใจซึ่งกันและกันสำหรับผู้ชมของคุณพิจารณา ...

  • โดยใช้คำศัพท์ที่ไม่ชัดเจน ("เราสามารถทำให้รหัสนี้ใช้ซ้ำสำหรับประเภทอื่นได้หรือไม่" หรือ "เราสามารถใช้การจัดส่งเสมือนได้หรือไม่" แทนที่จะเป็น "เราสามารถทำให้รหัสนี้มีความหลากหลายได้หรือไม่") และ / หรือ
  • กำหนดคำศัพท์ของคุณอย่างชัดเจน

ถึงกระนั้นสิ่งที่สำคัญในการเป็นโปรแกรมเมอร์ C ++ ที่ยอดเยี่ยมคือการทำความเข้าใจว่าพหุลักษณ์กำลังทำเพื่อคุณจริงๆ ...

    ให้คุณเขียนโค้ด "อัลกอริทึม" เพียงครั้งเดียวแล้วนำไปใช้กับข้อมูลหลายประเภท

... แล้วระวังให้ดีว่ากลไกต่าง ๆ ตรงกับความต้องการที่แท้จริงของคุณอย่างไร

ความหลากหลายแบบรันไทม์เหมาะกับ:

  • อินพุตที่ประมวลผลโดยวิธีการของโรงงานและพ่นออกมาเป็นคอลเลกชันวัตถุที่แตกต่างกันซึ่งจัดการผ่านBase*s,
  • การใช้งานที่เลือกในรันไทม์ตามไฟล์กำหนดค่าสวิตช์บรรทัดคำสั่งการตั้งค่า UI เป็นต้น
  • การใช้งานแตกต่างกันไปในขณะรันไทม์เช่นสำหรับรูปแบบเครื่องสถานะ

เมื่อไม่มีโปรแกรมควบคุมที่ชัดเจนสำหรับความหลากหลายของเวลาทำงานตัวเลือกเวลาคอมไพล์มักจะดีกว่า พิจารณา:

  • ลักษณะการคอมไพล์สิ่งที่เรียกว่าของคลาสเท็มเพลตนั้นดีกว่าสำหรับอินเทอร์เฟซ fat ที่ล้มเหลวในรันไทม์
  • SFINAE
  • CRTP
  • การเพิ่มประสิทธิภาพ (หลายอย่างรวมถึงการลบโค้ดอินไลน์และเดดโค้ดการคลายลูปอาร์เรย์แบบสแต็กแบบคงที่เทียบกับฮีป)
  • __FILE__, การต่อ__LINE__สายอักขระตามตัวอักษรและความสามารถเฉพาะอื่น ๆ ของมาโคร (ซึ่งยังคงชั่วร้าย ;-))
  • สนับสนุนการใช้งานความหมายของเทมเพลตและการทดสอบมาโคร แต่อย่า จำกัด วิธีการสนับสนุนที่มีให้โดยเทียม (เนื่องจากการจัดส่งเสมือนมีแนวโน้มที่จะต้องใช้การแทนที่ฟังก์ชันสมาชิกที่ตรงกันทุกประการ)

กลไกอื่น ๆ ที่สนับสนุนความหลากหลาย

ตามที่สัญญาไว้เพื่อความสมบูรณ์จะครอบคลุมหัวข้อต่อพ่วงหลายหัวข้อ:

  • โอเวอร์โหลดที่คอมไพเลอร์ให้มา
  • แปลง
  • ปลดเปลื้อง / การบังคับขู่เข็ญ

คำตอบนี้สรุปด้วยการอภิปรายว่าสิ่งที่กล่าวมาข้างต้นรวมกันเพื่อเสริมพลังและลดความซับซ้อนของรหัสโพลีมอร์ฟิสได้อย่างไรโดยเฉพาะความหลากหลายเชิงพาราเมตริก (เทมเพลตและมาโคร)

กลไกในการทำแผนที่เพื่อการดำเนินการเฉพาะประเภท

> โอเวอร์โหลดที่คอมไพเลอร์ระบุโดยนัย

ตามแนวคิดแล้วคอมไพเลอร์จะโอเวอร์โหลดตัวดำเนินการจำนวนมากสำหรับชนิดในตัว แนวคิดนี้ไม่แตกต่างจากการโอเวอร์โหลดที่ผู้ใช้ระบุ แต่มีการระบุไว้เนื่องจากมองข้ามได้ง่าย ตัวอย่างเช่นคุณสามารถเพิ่มในints และdoubles โดยใช้สัญกรณ์เดียวกันx += 2และคอมไพเลอร์สร้าง:

  • คำแนะนำ CPU เฉพาะประเภท
  • ผลของประเภทเดียวกัน

การโอเวอร์โหลดจะขยายไปยังประเภทที่ผู้ใช้กำหนดได้อย่างราบรื่น:

std::string x;
int y = 0;

x += 'c';
y += 'c';

การโอเวอร์โหลดที่คอมไพเลอร์จัดเตรียมไว้สำหรับประเภทพื้นฐานนั้นเป็นเรื่องปกติในภาษาคอมพิวเตอร์ระดับสูง (3GL +) และโดยทั่วไปการอภิปรายอย่างชัดเจนเกี่ยวกับความหลากหลายโดยทั่วไปจะมีความหมายมากกว่านั้น (2GLs - ภาษาแอสเซมบลี - มักต้องการให้โปรแกรมเมอร์ใช้การจำที่แตกต่างกันอย่างชัดเจนสำหรับประเภทต่างๆ)

> การแปลงมาตรฐาน

ส่วนที่สี่ของ C ++ Standard อธิบายถึง Conversion มาตรฐาน

ประเด็นแรกสรุปได้อย่างดี (จากร่างเก่า - หวังว่าจะยังคงถูกต้องอยู่):

-1- Conversion มาตรฐานคือการแปลงโดยนัยที่กำหนดไว้สำหรับประเภทบิวท์อิน Clause Convey จะแจกแจงชุดทั้งหมดของการแปลงดังกล่าว ลำดับการแปลงมาตรฐานคือลำดับของการแปลงมาตรฐานตามลำดับต่อไปนี้:

  • การแปลงเป็นศูนย์หรือหนึ่งรายการจากชุดต่อไปนี้: การแปลง lvalue-to-rvalue, การแปลงอาร์เรย์เป็นตัวชี้และการแปลงฟังก์ชันเป็นตัวชี้

  • การแปลงเป็นศูนย์หรือหนึ่งรายการจากชุดต่อไปนี้: การส่งเสริมการขายแบบรวมการส่งเสริมจุดลอยตัวการแปลงแบบรวมการแปลงจุดลอยตัวการแปลงการรวมแบบลอยตัวการแปลงตัวชี้การแปลงตัวชี้ไปยังการแปลงของสมาชิกและการแปลงบูลีน

  • การแปลงคุณสมบัติเป็นศูนย์หรือหนึ่งรายการ

[หมายเหตุ: ลำดับการแปลงมาตรฐานสามารถว่างเปล่าได้กล่าวคือไม่มี Conversion ] ลำดับการแปลงมาตรฐานจะถูกนำไปใช้กับนิพจน์หากจำเป็นเพื่อแปลงเป็นประเภทปลายทางที่ต้องการ

การแปลงเหล่านี้อนุญาตให้ใช้รหัสเช่น:

double a(double x) { return x + 2; }

a(3.14);
a(42);

ใช้การทดสอบก่อนหน้านี้:

จะเป็น polymorphic [ a()] จะต้องสามารถในการดำเนินงานที่มีค่าอย่างน้อยสองแตกต่างกันประเภท (เช่นintและdouble), การค้นหาและการดำเนินการพิมพ์รหัสที่เหมาะสม

a()ตัวมันเองรันโค้ดเฉพาะสำหรับdoubleดังนั้นจึงไม่ใช่ความหลากหลาย

แต่ในสายที่สองที่จะa()เรียบเรียงรู้เพื่อสร้างรหัสชนิดที่เหมาะสมสำหรับ "ลอยโปรโมชั่นจุด" (Standard §4) เพื่อแปลงไป42 42.0รหัสพิเศษนั้นอยู่ในฟังก์ชันการโทร เราจะพูดถึงความสำคัญของสิ่งนี้ในบทสรุป

> การบีบบังคับการร่ายการสร้างโดยนัย

กลไกเหล่านี้อนุญาตให้คลาสที่ผู้ใช้กำหนดเองเพื่อระบุพฤติกรรมคล้ายกับการแปลงมาตรฐานในตัว มาดูกัน:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

ที่นี่วัตถุstd::cinจะได้รับการประเมินในบริบทบูลีนด้วยความช่วยเหลือของตัวดำเนินการแปลง สิ่งนี้สามารถจัดกลุ่มตามแนวคิดด้วย "การส่งเสริมการขายแบบรวม" และอื่น ๆ จาก Conversion มาตรฐานในหัวข้อด้านบน

ตัวสร้างโดยนัยสามารถทำสิ่งเดียวกันได้อย่างมีประสิทธิภาพ แต่ถูกควบคุมโดยประเภท Cast-to:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

ผลกระทบของการโอเวอร์โหลดการแปลงและการบีบบังคับของคอมไพเลอร์

พิจารณา:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

ถ้าเราต้องการจำนวนเงินที่xได้รับการรักษาเป็นจำนวนจริงระหว่างส่วน (เช่นเป็น 6.5 มากกว่าลงกลม 6) เราเพียงtypedef double Amountต้องการการเปลี่ยนแปลง

เป็นสิ่งที่ดี แต่มันคงไม่ได้ผลมากเกินไปที่จะทำให้โค้ด "พิมพ์ถูกต้อง" อย่างชัดเจน:

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

แต่ให้พิจารณาว่าเราสามารถเปลี่ยนเวอร์ชันแรกให้เป็นtemplate:

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

เนื่องจาก "คุณสมบัติอำนวยความสะดวก" เพียงเล็กน้อยเหล่านั้นจึงสามารถสร้างอินสแตนซ์ได้อย่างง่ายดายสำหรับทั้งintหรือdoubleและทำงานตามที่ตั้งใจไว้ หากไม่มีคุณสมบัติเหล่านี้เราจำเป็นต้องมีการแคสต์ที่ชัดเจนประเภทลักษณะและ / หรือคลาสของนโยบายบางอย่างที่ละเอียดและผิดพลาดได้ง่ายเช่น:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

ดังนั้นตัวดำเนินการที่คอมไพเลอร์จัดเตรียมไว้ให้มากเกินไปสำหรับประเภทบิวท์อิน, การแปลงมาตรฐาน, การหล่อ / บีบบังคับ / คอนสตรัคเตอร์โดยนัย - ทั้งหมดนี้มีส่วนสนับสนุนอย่างละเอียดสำหรับความหลากหลาย จากคำจำกัดความที่ด้านบนของคำตอบนี้พวกเขากล่าวถึง "การค้นหาและเรียกใช้โค้ดที่เหมาะสมกับประเภท" โดยการจับคู่:

  • "away" จากประเภทพารามิเตอร์

    • จากข้อมูลหลายประเภทรหัสอัลกอริทึมโพลีมอร์ฟิกจัดการ

    • เป็นรหัสที่เขียนขึ้นสำหรับจำนวนประเภท (เหมือนกันหรืออื่น ๆ )

  • ประเภทพารามิเตอร์ "ถึง" จากค่าประเภทคงที่

พวกเขาไม่ได้สร้างบริบทที่หลากหลายด้วยตัวเอง แต่ช่วยเสริมพลัง / ลดความซับซ้อนของโค้ดภายในบริบทดังกล่าว

คุณอาจรู้สึกว่าถูกโกง ... ดูเหมือนจะไม่มากนัก ความสำคัญก็คือในบริบทพหุนามพาราเมตริก (เช่นภายในเทมเพลตหรือมาโคร) เรากำลังพยายามสนับสนุนประเภทที่มีขนาดใหญ่ตามอำเภอใจ แต่มักต้องการแสดงการดำเนินการกับฟังก์ชันเหล่านี้ในแง่ของฟังก์ชันตัวอักษรและการดำเนินการอื่น ๆ ที่ออกแบบมาสำหรับ ชุดเล็ก ๆ ช่วยลดความจำเป็นในการสร้างฟังก์ชันหรือข้อมูลที่ใกล้เคียงกันในแต่ละประเภทเมื่อการดำเนินการ / ค่ามีเหตุผลเหมือนกัน คุณลักษณะเหล่านี้ร่วมมือกันเพื่อเพิ่มทัศนคติของ "ความพยายามอย่างเต็มที่" ทำในสิ่งที่คาดหวังโดยสังหรณ์ใจโดยใช้ฟังก์ชันและข้อมูลที่มีอยู่อย่าง จำกัด และหยุดด้วยข้อผิดพลาดเมื่อมีความคลุมเครือจริงเท่านั้น

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


1
-1 คำตอบที่ดียกเว้นการอภิปรายเกี่ยวกับคำศัพท์ มาตรฐาน C ++ กำหนดคำว่า "polymorphic" ใน§1.8 / 1 โดยอ้างถึงส่วนที่ 10.3 เกี่ยวกับฟังก์ชันเสมือน ดังนั้นจึงไม่มีห้องกระดิกไม่มีห้องสำหรับการสนทนาไม่มีที่ว่างสำหรับความคิดเห็นส่วนตัว: ในบริบทของ C ++ มาตรฐานนั้นมีการกำหนดคำศัพท์ทุกครั้ง และมันมีบทบาทในทางปฏิบัติ ตัวอย่างเช่น§5.2.7 / 6 about dynamic_castต้องการ "ตัวชี้ไปที่หรือค่า lvalue ของประเภทความหลากหลาย" Cheers & hth.,
ไชโยและ hth. - Alf

@Alf: ข้อมูลอ้างอิงที่ดี - แม้ว่าฉันคิดว่ามุมมองของคุณแคบเกินไป เป็นที่ชัดเจนมากจากคำถามที่ระบุถึงการโอเวอร์โหลด ad-hoc และความหลากหลายเชิงพาราเมตริกเป็นต้นว่าคำตอบควรเกี่ยวข้องกับความสามารถของ C ++ กับ Comp ทั่วไป วิทย์ ความหมายของเงื่อนไข อภิธานศัพท์ของ Stroustrup กล่าวว่า "polymorphism - ให้อินเทอร์เฟซเดียวสำหรับเอนทิตีประเภทต่างๆฟังก์ชันเสมือนจะให้ความหลากหลายแบบไดนามิก (รันไทม์) ผ่านอินเทอร์เฟซที่จัดเตรียมโดยคลาสฐานฟังก์ชันและเทมเพลตที่มากเกินไปให้ความหลากหลายแบบคงที่ (เวลาคอมไพล์ - ไทม์) TC ++ PL 12.2.6, 13.6.1, D&E 2.9. "
Tony Delroy

@ โทนี่: มันไม่ใช่แรงผลักดันหลักของคำตอบของคุณนั้นผิด ก็โอเคดีมาก มันก็แค่นั้นเอง คำศัพท์ที่คุณเข้าใจย้อนกลับไป: คำศัพท์ทางวิชาการที่เป็นทางการคือคำศัพท์ที่แคบซึ่งกำหนดโดยมาตรฐานสากลศักดิ์สิทธิ์และคำศัพท์คร่าวๆที่ไม่เป็นทางการซึ่งผู้คนอาจหมายถึงสิ่งที่แตกต่างกันเล็กน้อยเป็นคำที่ใช้เป็นหลักในคำถามและคำตอบนี้ Cheers & hth.,
ไชโยและ hth. - Alf

@ อัลฟ์: ฉันหวังว่าคำตอบจะดีมาก - "กลไกอื่น ๆ " จำเป็นต้องเขียนใหม่ในหนึ่งในห้าของบรรทัดและฉันกำลังพิจารณา / ร่างคุณลักษณะและผลกระทบที่เป็นรูปธรรมมากขึ้นซึ่งแตกต่างจากกลไกเชิงพหุนาม อย่างไรก็ตามความเข้าใจของฉันก็คือความหมายที่เน้นเฉพาะ C ++ ในเชิงวิชาการอย่างเป็นทางการอาจจะแคบ แต่ Comp. วิทย์ ความหมายไม่ได้เป็นหลักฐานตามอภิธานศัพท์ของ Stroustrup เราต้องการบางสิ่งที่ชัดเจนเช่นคำจำกัดความจาก Knuth - ยังไม่มีโชค ฉันขอบคุณที่คุณเป็นกูรูด้าน C ++ แต่คุณสามารถชี้หลักฐานที่เกี่ยวข้องกับเรื่องนี้โดยเฉพาะได้หรือไม่
Tony Delroy

1
@ อัลฟ์: ประการที่สองฉันมั่นใจว่าความหลากหลายนั้นถูกกำหนดอย่างเป็นทางการใน Comp ทั่วไปที่เหมาะสม วิทย์ จองด้วยวิธี (เหนือกาลเวลาและเสถียร) ที่เข้ากันได้กับการใช้งานของฉัน (และของ Stroustrup) บทความ Wikipedia เชื่อมโยงสิ่งพิมพ์ทางวิชาการสองสามฉบับที่กำหนดไว้ในลักษณะนั้น: "ฟังก์ชัน Polymorphic คือฟังก์ชันที่ตัวถูกดำเนินการ (พารามิเตอร์จริง) สามารถมีได้มากกว่าหนึ่งประเภทประเภท Polymorphic คือประเภทที่การดำเนินการใช้ได้กับค่ามากกว่าหนึ่งประเภท" (จากlucacardelli.name/Papers/OnUnderstand.A4.pdf ) ดังนั้นคำถามคือ "ใครเป็นคนพูดแทนวิทย์" ... ?
Tony Delroy

15

ใน C ++ ความแตกต่างที่สำคัญคือรันไทม์เทียบกับการรวมเวลาคอมไพล์ Ad-hoc เทียบกับพาราเมตริกไม่ได้ช่วยได้จริง ๆ อย่างที่ฉันจะอธิบายในภายหลัง

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

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

การโอเวอร์โหลดของฟังก์ชันและการโอเวอร์โหลดของตัวดำเนินการเป็นสิ่งเดียวกันในทุก ๆ ด้านที่สำคัญ ชื่อและไวยากรณ์สำหรับใช้ไม่มีผลต่อความหลากหลาย

เทมเพลตช่วยให้คุณระบุฟังก์ชันที่มากเกินไปในคราวเดียว

มีชื่ออีกชุดสำหรับแนวคิดเวลาความละเอียดเดียวกัน ...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

ชื่อเหล่านี้มีความเกี่ยวข้องกับ OOP มากกว่าดังนั้นจึงเป็นเรื่องแปลกที่จะบอกว่าเทมเพลตหรือฟังก์ชันอื่น ๆ ที่ไม่ใช่สมาชิกใช้การเชื่อมโยงในช่วงต้น

เพื่อให้เข้าใจถึงความสัมพันธ์ระหว่างฟังก์ชันเสมือนจริงและการทำงานมากเกินไปนอกจากนี้ยังมีประโยชน์ในการทำความเข้าใจความแตกต่างระหว่าง "การจัดส่งครั้งเดียว" และ "การจัดส่งหลายรายการ" ความคิดเข้าใจได้ว่าเป็นความก้าวหน้า ...

  • ประการแรกมีฟังก์ชัน monomorphic การนำฟังก์ชันไปใช้งานจะถูกระบุโดยไม่ซ้ำกันด้วยชื่อฟังก์ชัน ไม่มีพารามิเตอร์ใดเป็นพิเศษ
  • จากนั้นมีการจัดส่งเพียงครั้งเดียว พารามิเตอร์ตัวหนึ่งถือเป็นพารามิเตอร์พิเศษและใช้ (พร้อมกับชื่อ) เพื่อระบุว่าจะใช้การนำไปใช้งานใด ใน OOP เรามักจะคิดว่าพารามิเตอร์นี้เป็น "วัตถุ" โดยแสดงรายการก่อนชื่อฟังก์ชันเป็นต้น
  • จากนั้นมีการจัดส่งหลายครั้ง พารามิเตอร์ใด ๆ / ทั้งหมดมีส่วนช่วยในการระบุว่าจะใช้การนำไปใช้งานใด ดังนั้นอีกครั้งไม่มีพารามิเตอร์ใดที่จำเป็นต้องเป็นพิเศษ

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

เช่นเดียวกับการใช้คำว่า "early binding" สำหรับฟังก์ชันที่ไม่ใช่สมาชิกจึงเป็นเรื่องแปลกที่จะใช้คำว่า "single dispatch" และ "multiple dispatch" ซึ่งความหลากหลายจะได้รับการแก้ไขในเวลาคอมไพล์ โดยปกติแล้ว C ++ จะถือว่าไม่มีการจัดส่งหลายครั้งซึ่งถือเป็นการแก้ปัญหาขณะทำงานโดยเฉพาะ อย่างไรก็ตามการโอเวอร์โหลดฟังก์ชันสามารถมองได้ว่าเป็นการจัดส่งหลายครั้งในเวลาคอมไพล์

การกลับไปใช้ความหลากหลายเชิงพาราเมตริกเทียบกับ ad-hoc คำศัพท์เหล่านี้ได้รับความนิยมมากขึ้นในการเขียนโปรแกรมเชิงฟังก์ชันและใช้ไม่ได้กับ C ++ ถึงอย่างนั้น ...

ความหลากหลายเชิงพาราเมตริกหมายความว่าคุณมีประเภทเป็นพารามิเตอร์และมีการใช้รหัสเดียวกันทุกประการโดยไม่คำนึงถึงประเภทที่คุณใช้สำหรับพารามิเตอร์เหล่านั้น

Ad-hoc polymorphism เป็น ad-hoc ในแง่ที่คุณระบุรหัสที่แตกต่างกันขึ้นอยู่กับประเภทเฉพาะ

ฟังก์ชันโอเวอร์โหลดและเวอร์ชวลเป็นตัวอย่างของความหลากหลายของ ad-hoc polymorphism

อีกครั้งมีคำพ้องความหมาย ...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

ยกเว้นคำเหล่านี้ไม่ใช่คำพ้องความหมายแม้ว่าโดยทั่วไปจะถือว่าเป็นเหมือนกันและนั่นคือจุดที่ความสับสนน่าจะเกิดขึ้นใน C ++

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

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

ในเช่น Haskell คุณสามารถมี ...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

aนี่เป็นข้อ จำกัด ประเภท polymorphic มันอาจเป็นอะไรก็ได้ดังนั้นเราจึงไม่สามารถทำอะไรกับค่าประเภทนั้นได้มากนัก

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

ที่นี่aถูก จำกัด ให้เป็นสมาชิกของNumชั้นเรียน - ประเภทที่ทำหน้าที่เหมือนตัวเลข ข้อ จำกัด ดังกล่าวช่วยให้คุณทำสิ่งที่เป็นตัวเลขด้วยค่าเหล่านั้นได้เช่นเพิ่ม แม้3เป็น polymorphic - อนุมานชนิดตัวเลขออกมาว่าคุณหมายถึงประเภท3a

ฉันคิดว่านี่เป็นความหลากหลายเชิงพาราเมตริกที่ จำกัด มีการใช้งานเพียงครั้งเดียว แต่สามารถใช้ได้ในกรณีที่มีข้อ จำกัด เท่านั้น ด้านเฉพาะกิจคือการเลือก+และ3ใช้ "อินสแตนซ์" แต่ละรายการNumมีการใช้งานที่แตกต่างกันของสิ่งเหล่านี้ ดังนั้นแม้ใน Haskell "พาราเมตริก" และ "ไม่ถูก จำกัด " ก็ไม่ได้มีความหมายเหมือนกัน - อย่าโทษฉันมันไม่ใช่ความผิดของฉัน!

ใน C ++ ทั้งการโอเวอร์โหลดและฟังก์ชันเสมือนเป็นความหลากหลายแบบเฉพาะกิจ คำจำกัดความของ ad-hoc polymorphism ไม่สนใจว่าการใช้งานจะถูกเลือกในรันไทม์หรือเวลาคอมไพล์

C ++ เข้าใกล้ความหลากหลายเชิงพาราเมตริกด้วยเทมเพลตหากทุกพารามิเตอร์ของเทมเพลตมีประเภท typenameได้รับมากใกล้เคียงกับความแตกต่างกับแม่แบบพาราถ้าทุกพารามิเตอร์แม่แบบมีประเภท มีพารามิเตอร์ประเภทและมีการใช้งานเพียงครั้งเดียวไม่ว่าจะใช้ประเภทใดก็ตาม อย่างไรก็ตามกฎ "การแทนที่ล้มเหลวไม่ใช่ข้อผิดพลาด" หมายความว่าข้อ จำกัด โดยนัยเกิดขึ้นจากการใช้การดำเนินการภายในเทมเพลต ภาวะแทรกซ้อนเพิ่มเติม ได้แก่ ความเชี่ยวชาญพิเศษของเทมเพลตสำหรับการจัดหาเทมเพลตทางเลือก - การใช้งาน (เฉพาะกิจ) ที่แตกต่างกัน

ดังนั้น C ++ มีความหลากหลายเชิงพาราเมตริก แต่มีข้อ จำกัด โดยปริยายและอาจถูกแทนที่ด้วยทางเลือกเฉพาะกิจนั่นคือการจำแนกประเภทนี้ใช้ไม่ได้กับ C ++


+1 ประเด็นและข้อมูลเชิงลึกที่น่าสนใจมากมาย ฉันใช้เวลาเพียงไม่กี่ชั่วโมงในการอ่านเกี่ยวกับ Haskell ดังนั้น " aที่นี่เป็นโพลีมอร์ฟิกที่ไม่มีข้อ จำกัด [... ] ดังนั้นเราจึงไม่สามารถทำอะไรกับค่าประเภทนั้นได้มากนัก" เป็นที่สนใจ - ใน C ++ sans Concepts คุณไม่ได้ถูก จำกัด ให้พยายามเฉพาะชุดของการดำเนินการเฉพาะกับอาร์กิวเมนต์ของประเภทที่ระบุเป็นพารามิเตอร์เทมเพลต ... คุณระบุแทนที่จะป้องกันการใช้การดำเนินการเพิ่มเติมโดยไม่ได้ตั้งใจ
Tony Delroy

@Tony - แนวคิดเป็นวิธีการ จำกัด ความหลากหลายของเทมเพลตอย่างชัดเจน ข้อ จำกัด โดยนัยเห็นได้ชัดว่าจะไม่หายไปเนื่องจากความเข้ากันได้ แต่ข้อ จำกัด ที่ชัดเจนจะช่วยปรับปรุงสิ่งต่างๆได้อย่างมีนัยสำคัญ ฉันค่อนข้างแน่ใจว่าแผนสำหรับแนวคิดในอดีตบางส่วนค่อนข้างเกี่ยวข้องกับแว่นพิมพ์ดีดของ Haskell แม้ว่าฉันจะไม่ได้มองลึกลงไปขนาดนั้นและเมื่อฉันดู "ตื้น ๆ " ครั้งสุดท้ายฉันก็ไม่รู้จัก Haskell มากนัก
Steve314

"ข้อ จำกัด โดยปริยายเห็นได้ชัดว่าจะไม่หายไปเนื่องจากความเข้ากันได้" - จากหน่วยความจำแนวคิด C ++ 0x ทำ (สัญญาว่าจะ: - /) ป้องกัน "ข้อ จำกัด โดยนัย" - คุณสามารถใช้ประเภทในรูปแบบที่สัญญาไว้โดยแนวคิดเท่านั้น
Tony Delroy

2

สำหรับความแตกต่างแบบเฉพาะกิจหมายถึงฟังก์ชันโอเวอร์โหลดหรือตัวดำเนินการมากเกินไป ตรวจสอบที่นี่:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

สำหรับความหลากหลายเชิงพาราเมตริกสามารถนับฟังก์ชันเทมเพลตได้เนื่องจากไม่จำเป็นต้องใช้พารามิเตอร์ประเภท FIXED ตัวอย่างเช่นฟังก์ชันหนึ่งสามารถจัดเรียงอาร์เรย์ของจำนวนเต็มและยังสามารถจัดเรียงอาร์เรย์ของสตริงได้เป็นต้น

http://en.wikipedia.org/wiki/Parametric_polymorphism


1
น่าเสียดายที่แม้ว่าจะถูกต้อง แต่ก็ทำให้เข้าใจผิด ฟังก์ชันเทมเพลตอาจมีข้อ จำกัด โดยปริยายเนื่องจากกฎ SFINAE - การใช้การดำเนินการภายในเทมเพลตจะ จำกัด ความหลากหลายโดยปริยาย - และความเชี่ยวชาญพิเศษของเทมเพลตสามารถให้เทมเพลตทางเลือกเฉพาะกิจที่แทนที่เทมเพลตทั่วไปได้ ดังนั้นเทมเพลต (โดยค่าเริ่มต้น) จะให้ความหลากหลายของพาราเมตริกที่ไม่มีข้อ จำกัด แต่ไม่มีการบังคับใช้ - มีอย่างน้อยสองวิธีที่สามารถกลายเป็นข้อ จำกัด หรือเฉพาะกิจได้
Steve314

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

2

นี่อาจจะไม่ช่วยอะไร แต่ฉันทำขึ้นเพื่อแนะนำเพื่อนของฉันเกี่ยวกับการเขียนโปรแกรมโดยให้ฟังก์ชันที่กำหนดไว้เช่นSTARTและENDสำหรับฟังก์ชันหลักเพื่อไม่ให้น่ากลัวเกินไป (พวกเขาใช้เฉพาะmain.cpp ) ประกอบด้วยคลาสและโครงสร้างโพลีมอร์ฟิสแม่แบบเวกเตอร์อาร์เรย์คำสั่งพรีโปรเซสเซอร์มิตรภาพตัวดำเนินการและตัวชี้ (ซึ่งทั้งหมดนี้คุณควรทราบก่อนที่จะลองใช้ความหลากหลาย):

หมายเหตุ: ยังไม่เสร็จสิ้น แต่คุณสามารถรับแนวคิดได้

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

1

นี่คือตัวอย่างพื้นฐานที่ใช้คลาส Polymorphic

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}

0

Polymorphism หมายถึงหลายรูปแบบซึ่งใช้สำหรับตัวดำเนินการในการทำงานที่แตกต่างกันภายใต้อินสแตนซ์ที่แตกต่างกัน Polymorphism ใช้ในการถ่ายทอดลักษณะทางพันธุกรรม ตัวอย่างเช่นเราได้กำหนด fn draw () สำหรับรูปร่างคลาสแล้วสามารถใช้การวาด fn สำหรับการวาดวงกลมกล่องสามเหลี่ยมและรูปร่างอื่น ๆ (ซึ่งเป็นวัตถุที่มีรูปร่างคลาส)


-3

ถ้าใครพูดว่า CUT กับคนเหล่านี้

The Surgeon
The Hair Stylist
The Actor

อะไรจะเกิดขึ้น?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

ดังนั้นการเป็นตัวแทนด้านบนจะแสดงความหลากหลายคืออะไร (ชื่อเดียวกันพฤติกรรมต่างกัน) ใน OOP

หากคุณกำลังจะสัมภาษณ์และผู้สัมภาษณ์ขอให้คุณบอก / แสดงตัวอย่างสดสำหรับความหลากหลายในห้องเดียวกับที่เรานั่งอยู่ให้พูด -

คำตอบ - ประตู / Windows

สงสัยว่าอย่างไร?

ทางประตู / หน้าต่าง - คนสามารถมาได้อากาศมาได้แสงมาฝนมาได้ ฯลฯ

กล่าวคือพฤติกรรมที่แตกต่างกันรูปแบบหนึ่ง (Polymorphism)

เพื่อให้เข้าใจได้ดีขึ้นและในลักษณะง่ายๆฉันใช้ตัวอย่างข้างต้น .. หากคุณต้องการข้อมูลอ้างอิงสำหรับโค้ดให้ทำตามคำตอบด้านบน


ดังที่ได้กล่าวไว้เพื่อความเข้าใจที่ดีขึ้นเกี่ยวกับ Polymorphism ใน c ++ ฉันใช้ตัวอย่างข้างต้น สิ่งนี้อาจช่วยให้เด็กใหม่เข้าใจและเชื่อมโยงความหมายได้จริงหรือสิ่งที่เกิดขึ้นหลังโค้ดขณะทำการสัมภาษณ์ ขอบคุณ!
Sanchit

op ถามว่า "polymorphism ใน c ++" คำตอบของคุณเป็นนามธรรมเกินไป
StahlRat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.