ความเข้าใจเกี่ยวกับ / ข้อกำหนดสำหรับความหลากหลาย
เพื่อทำความเข้าใจเกี่ยวกับความหลากหลาย - เป็นคำที่ใช้ในวิทยาการคอมพิวเตอร์ - ช่วยให้เริ่มต้นจากการทดสอบและนิยามของคำศัพท์ง่ายๆ พิจารณา:
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
กลไกอื่น ๆ ที่เกี่ยวข้อง
ความหลากหลายของคอมไพเลอร์ที่จัดเตรียมไว้สำหรับประเภทบิวด์อินการแปลงมาตรฐานและการหล่อ / การบีบบังคับจะกล่าวถึงในภายหลังเพื่อความสมบูรณ์ดังนี้:
- พวกเขามักจะเข้าใจโดยสัญชาตญาณอยู่แล้ว (รับประกันว่า " โอ้นั่นปฏิกิริยา ")
- พวกเขาส่งผลกระทบต่อเกณฑ์ในการกำหนดและความราบรื่นในการใช้กลไกข้างต้นและ
- คำอธิบายเป็นสิ่งที่ทำให้ไขว้เขวจากแนวคิดที่สำคัญกว่า
คำศัพท์
การจัดหมวดหมู่เพิ่มเติม
ด้วยกลไกหลายรูปแบบด้านบนเราสามารถจัดหมวดหมู่ได้หลายวิธี:
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__
สายอักขระตามตัวอักษรและความสามารถเฉพาะอื่น ๆ ของมาโคร (ซึ่งยังคงชั่วร้าย ;-))
- สนับสนุนการใช้งานความหมายของเทมเพลตและการทดสอบมาโคร แต่อย่า จำกัด วิธีการสนับสนุนที่มีให้โดยเทียม (เนื่องจากการจัดส่งเสมือนมีแนวโน้มที่จะต้องใช้การแทนที่ฟังก์ชันสมาชิกที่ตรงกันทุกประการ)
กลไกอื่น ๆ ที่สนับสนุนความหลากหลาย
ตามที่สัญญาไว้เพื่อความสมบูรณ์จะครอบคลุมหัวข้อต่อพ่วงหลายหัวข้อ:
- โอเวอร์โหลดที่คอมไพเลอร์ให้มา
- แปลง
- ปลดเปลื้อง / การบังคับขู่เข็ญ
คำตอบนี้สรุปด้วยการอภิปรายว่าสิ่งที่กล่าวมาข้างต้นรวมกันเพื่อเสริมพลังและลดความซับซ้อนของรหัสโพลีมอร์ฟิสได้อย่างไรโดยเฉพาะความหลากหลายเชิงพาราเมตริก (เทมเพลตและมาโคร)
กลไกในการทำแผนที่เพื่อการดำเนินการเฉพาะประเภท
> โอเวอร์โหลดที่คอมไพเลอร์ระบุโดยนัย
ตามแนวคิดแล้วคอมไพเลอร์จะโอเวอร์โหลดตัวดำเนินการจำนวนมากสำหรับชนิดในตัว แนวคิดนี้ไม่แตกต่างจากการโอเวอร์โหลดที่ผู้ใช้ระบุ แต่มีการระบุไว้เนื่องจากมองข้ามได้ง่าย ตัวอย่างเช่นคุณสามารถเพิ่มในint
s และdouble
s โดยใช้สัญกรณ์เดียวกัน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;
}
ดังนั้นตัวดำเนินการที่คอมไพเลอร์จัดเตรียมไว้ให้มากเกินไปสำหรับประเภทบิวท์อิน, การแปลงมาตรฐาน, การหล่อ / บีบบังคับ / คอนสตรัคเตอร์โดยนัย - ทั้งหมดนี้มีส่วนสนับสนุนอย่างละเอียดสำหรับความหลากหลาย จากคำจำกัดความที่ด้านบนของคำตอบนี้พวกเขากล่าวถึง "การค้นหาและเรียกใช้โค้ดที่เหมาะสมกับประเภท" โดยการจับคู่:
พวกเขาไม่ได้สร้างบริบทที่หลากหลายด้วยตัวเอง แต่ช่วยเสริมพลัง / ลดความซับซ้อนของโค้ดภายในบริบทดังกล่าว
คุณอาจรู้สึกว่าถูกโกง ... ดูเหมือนจะไม่มากนัก ความสำคัญก็คือในบริบทพหุนามพาราเมตริก (เช่นภายในเทมเพลตหรือมาโคร) เรากำลังพยายามสนับสนุนประเภทที่มีขนาดใหญ่ตามอำเภอใจ แต่มักต้องการแสดงการดำเนินการกับฟังก์ชันเหล่านี้ในแง่ของฟังก์ชันตัวอักษรและการดำเนินการอื่น ๆ ที่ออกแบบมาสำหรับ ชุดเล็ก ๆ ช่วยลดความจำเป็นในการสร้างฟังก์ชันหรือข้อมูลที่ใกล้เคียงกันในแต่ละประเภทเมื่อการดำเนินการ / ค่ามีเหตุผลเหมือนกัน คุณลักษณะเหล่านี้ร่วมมือกันเพื่อเพิ่มทัศนคติของ "ความพยายามอย่างเต็มที่" ทำในสิ่งที่คาดหวังโดยสังหรณ์ใจโดยใช้ฟังก์ชันและข้อมูลที่มีอยู่อย่าง จำกัด และหยุดด้วยข้อผิดพลาดเมื่อมีความคลุมเครือจริงเท่านั้น
สิ่งนี้ช่วย จำกัด ความจำเป็นในการใช้รหัสโพลีมอร์ฟิสที่รองรับรหัสโพลีมอร์ฟิสการวาดตาข่ายที่แน่นขึ้นเกี่ยวกับการใช้ความหลากหลายเพื่อให้การใช้งานที่แปลเป็นภาษาท้องถิ่นไม่ได้บังคับให้มีการใช้งานอย่างแพร่หลายและทำให้ประโยชน์ของความหลากหลายตามความจำเป็นโดยไม่ต้องเสียค่าใช้จ่ายในการเปิดเผยการนำไปใช้ที่ เวลาคอมไพล์มีสำเนาหลายชุดของฟังก์ชันลอจิคัลเดียวกันในรหัสวัตถุเพื่อรองรับชนิดที่ใช้และในการทำการจัดส่งเสมือนซึ่งต่างจากการเรียกแบบอินไลน์หรืออย่างน้อยการเรียกที่แก้ไขเวลาคอมไพล์ ตามปกติในภาษา C ++ โปรแกรมเมอร์จะได้รับอิสระอย่างมากในการควบคุมขอบเขตที่ใช้ความหลากหลาย