รูปแบบการเข้ารหัสเป็นอัตนัยในที่สุดและเป็นไปได้ยากมากที่ผลประโยชน์ด้านประสิทธิภาพจะมาจากมัน แต่นี่คือสิ่งที่ฉันจะบอกว่าคุณได้รับจากการใช้การเริ่มต้นแบบเสรี:
ลดขนาดชื่อซ้ำซ้อนให้เหลือน้อยที่สุด
พิจารณาสิ่งต่อไปนี้:
vec3 GetValue()
{
return vec3(x, y, z);
}
ทำไมฉันต้องพิมพ์vec3
สองครั้ง มีประเด็นที่ว่า? คอมไพเลอร์รู้ดีว่าฟังก์ชันส่งคืนได้ดีเพียงใด ทำไมฉันไม่สามารถพูดว่า "เรียกตัวสร้างของสิ่งที่ฉันกลับมาด้วยค่าเหล่านี้และส่งคืนได้หรือไม่" ด้วยการเริ่มต้นที่สม่ำเสมอฉันสามารถ:
vec3 GetValue()
{
return {x, y, z};
}
ทุกอย่างทำงานได้
ดียิ่งขึ้นสำหรับฟังก์ชั่นการขัดแย้ง พิจารณาสิ่งนี้:
void DoSomething(const std::string &str);
DoSomething("A string.");
ใช้งานได้โดยไม่ต้องพิมพ์ชื่อเพราะstd::string
รู้วิธีสร้างตัวเองconst char*
โดยปริยาย เยี่ยมมาก แต่ถ้าสตริงนั้นมาจากอะไรให้พูด RapidXML หรือสตริงลัวะ นั่นคือสมมุติว่าฉันรู้ความจริงของความยาวของสตริงที่อยู่ข้างหน้า std::string
คอนสตรัคที่ใช้จะต้องใช้ความยาวของสตริงถ้าฉันเพียงแค่ผ่านconst char*
const char*
มีโอเวอร์โหลดที่ใช้ความยาวอย่างชัดเจนว่าเป็น DoSomething(std::string(strValue, strLen))
แต่การที่จะใช้มันฉันต้องทำเช่นนี้: ทำไมต้องพิมพ์ชื่อพิเศษในนั้น คอมไพเลอร์รู้ว่าประเภทคืออะไร เช่นเดียวกับauto
เราสามารถหลีกเลี่ยงการพิมพ์ชื่อพิเศษ:
DoSomething({strValue, strLen});
มันใช้งานได้ ไม่มีชื่อพิมพ์ไม่ต้องยุ่งยากอะไร คอมไพเลอร์ทำงานได้รหัสสั้นและทุกคนก็มีความสุข
ได้รับมีข้อโต้แย้งที่จะทำให้รุ่นแรก ( DoSomething(std::string(strValue, strLen))
) ชัดเจนมากขึ้น นั่นคือมันชัดเจนว่าเกิดอะไรขึ้นและใครทำอะไร นั่นเป็นเรื่องจริง การทำความเข้าใจโค้ดที่ใช้การกำหนดค่าเริ่มต้นที่สม่ำเสมอนั้นต้องดูที่ต้นแบบฟังก์ชัน นี่คือเหตุผลเดียวกันว่าทำไมบางคนบอกว่าคุณไม่ควรผ่านพารามิเตอร์โดยการอ้างอิงที่ไม่ใช่ const: เพื่อให้คุณสามารถดูได้ที่ไซต์การโทรหากมีการแก้ไขค่า
แต่ที่เหมือนกันอาจจะกล่าวว่าสำหรับauto
; รู้ว่าสิ่งที่คุณได้รับจากต้องมองไปที่ความหมายของauto v = GetSomething();
GetSomething
แต่นั่นก็ไม่ได้หยุดauto
จากการใช้งานกับการถูกละทิ้งโดยประมาทเมื่อคุณสามารถเข้าถึงได้ โดยส่วนตัวแล้วฉันคิดว่ามันจะไม่เป็นไรเมื่อคุณชินกับมันแล้ว โดยเฉพาะอย่างยิ่งกับ IDE ที่ดี
ไม่เคยได้รับการแยกวิเคราะห์ Vexing มากที่สุด
นี่คือรหัสบางส่วน
class Bar;
void Func()
{
int foo(Bar());
}
คำถามป๊อป: อะไรนะfoo
? หากคุณตอบว่า "ตัวแปร" คุณผิด อันที่จริงมันเป็นต้นแบบของฟังก์ชันที่ใช้เป็นพารามิเตอร์ของฟังก์ชันที่ส่งคืน a Bar
และfoo
ค่าส่งคืนของฟังก์ชันนั้นเป็น int
สิ่งนี้เรียกว่า "Most Vexing Parse" ของ C ++ เพราะมันไม่สมเหตุสมผลกับมนุษย์ แต่กฎของ C ++ น่าเศร้าต้องการสิ่งนี้ถ้ามันสามารถตีความได้ว่าเป็นฟังก์ชั่นต้นแบบแล้วมันจะเป็น ปัญหาคือBar()
; นั่นอาจเป็นหนึ่งในสองสิ่ง อาจเป็นชื่อที่มีประเภทBar
ซึ่งหมายความว่ามันกำลังสร้างชั่วคราว Bar
หรือมันอาจจะเป็นฟังก์ชั่นที่จะไม่มีพารามิเตอร์และผลตอบแทนที่
การกำหนดค่าเริ่มต้นที่เหมือนกันไม่สามารถตีความได้ว่าเป็นฟังก์ชันต้นแบบ:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
สร้างชั่วคราวเสมอ int foo{...}
สร้างตัวแปรเสมอ
มีหลายกรณีที่คุณต้องการใช้Typename()
แต่ไม่สามารถทำได้เนื่องจากกฎการแยกวิเคราะห์ของ C ++ ด้วยTypename{}
ไม่มีความกำกวม
เหตุผลที่ไม่ควร
พลังที่แท้จริงเพียงอย่างเดียวที่คุณยอมแพ้คือการ จำกัด คุณไม่สามารถเริ่มต้นค่าที่น้อยลงด้วยค่าที่ใหญ่กว่าด้วยการกำหนดค่าเริ่มต้นที่สม่ำเสมอ
int val{5.2};
ที่จะไม่คอมไพล์ คุณสามารถทำได้ด้วยการกำหนดค่าเริ่มต้นแบบเก่า แต่ไม่ใช่การกำหนดค่าเริ่มต้นที่สม่ำเสมอ
นี่เป็นส่วนหนึ่งที่ทำให้รายการเริ่มต้นใช้งานได้จริง มิฉะนั้นจะมีกรณีที่ไม่ชัดเจนจำนวนมากเกี่ยวกับประเภทของรายการเริ่มต้น
แน่นอนว่าบางคนอาจโต้แย้งว่ารหัสดังกล่าวไม่สมควรรวบรวม โดยส่วนตัวฉันเห็นด้วย; การกวดขันเป็นสิ่งที่อันตรายมากและอาจนำไปสู่พฤติกรรมที่ไม่พึงประสงค์ อาจเป็นการดีที่สุดที่จะแก้ไขปัญหาเหล่านั้นตั้งแต่เนิ่น ๆ อย่างน้อยที่สุดการ จำกัด ให้แคบลงแสดงให้เห็นว่าบางคนไม่ได้คิดมากเรื่องรหัส
โปรดสังเกตว่าคอมไพเลอร์โดยทั่วไปจะเตือนคุณเกี่ยวกับสิ่งนี้หากระดับการเตือนของคุณสูง ดังนั้นทั้งหมดนี้จะทำให้คำเตือนเป็นข้อผิดพลาดที่บังคับใช้ บางคนอาจบอกว่าคุณควรทำอย่างนั้น;)
มีอีกเหตุผลหนึ่งที่ไม่:
std::vector<int> v{100};
สิ่งนี้ทำอะไร มันสามารถสร้างรายการที่vector<int>
มีค่าเริ่มต้นที่สร้างขึ้นหนึ่งร้อยรายการ หรืออาจสร้างvector<int>
ด้วย 1 100
รายการที่เป็นค่า ทั้งสองเป็นไปได้ในทางทฤษฎี
ในความเป็นจริงมันทำหลัง
ทำไม? รายการเริ่มต้นใช้ไวยากรณ์เดียวกับการเริ่มต้นเครื่องแบบ ดังนั้นจะต้องมีกฎบางอย่างเพื่ออธิบายสิ่งที่ต้องทำในกรณีที่มีความกำกวม กฎง่ายๆคือสวย: ถ้าคอมไพเลอร์สามารถใช้ตัวสร้างรายการ initializer กับรายการรั้ง-เริ่มต้นแล้วมันจะ นับตั้งแต่vector<int>
มีการเริ่มต้นรายการคอนสตรัคที่ใช้เวลาinitializer_list<int>
และ {100} อาจจะเป็นที่ถูกต้องinitializer_list<int>
มันจึงต้องเป็น
เพื่อให้ได้รับการปรับขนาดตัวสร้างคุณต้องใช้แทน()
{}
โปรดทราบว่าหากนี่เป็นvector
สิ่งที่ไม่สามารถแปลงเป็นจำนวนเต็มได้จะไม่เกิดขึ้น initializer_list จะไม่พอดีกับตัวสร้างรายการ initializer ของvector
ประเภทนั้นและคอมไพเลอร์จะมีอิสระที่จะเลือกจากตัวสร้างอื่น ๆ