คำสั่ง“ return {}” ใน C ++ 11 หมายถึงอะไร?


115

คำสั่งอะไร

return {};

ใน C ++ 11 ระบุและเมื่อใดควรใช้แทน (พูด)

return NULL;

หรือ

return nullptr;

59
มันส่งคืนอินสแตนซ์ที่สร้างขึ้นโดยปริยายของประเภทการส่งคืนของฟังก์ชัน
Richard Hodges

หรือมันเรียบง่ายreturn;โดยไม่มีค่า?
i486

ไม่ตามที่การอภิปรายเปิดเผยมันเป็นข้อผิดพลาดเวลาคอมไพล์หากฟังก์ชันของคุณควรส่งคืนบางสิ่งบางอย่าง (เช่นไม่ใช่ประเภทการส่งคืนที่เป็นโมฆะ) และคุณเขียนเพียงอย่างเดียวreturn; ในทางกลับกันreturn{};ก็ใช้ได้หากคุณมีประเภทการส่งคืน
เดีย

@ สื่อไม่เสมอไปวัตถุบางอย่างจะต้องใช้อาร์กิวเมนต์ในการสร้าง
MM

คำตอบ:


108

return {};ระบุว่า "ส่งคืนอ็อบเจ็กต์ของประเภทการส่งคืนของฟังก์ชันที่เริ่มต้นด้วยรายการเริ่มต้นที่ว่างเปล่า" พฤติกรรมที่แน่นอนขึ้นอยู่กับชนิดของวัตถุที่ส่งคืน

จากcppreference.com (เนื่องจาก OP ถูกแท็ก C ++ 11 ฉันจึงไม่รวมกฎใน C ++ 14 และ C ++ 17 โปรดดูลิงค์สำหรับรายละเอียดเพิ่มเติม):

  • ถ้า braced-init-list ว่างเปล่าและ T เป็นประเภทคลาสที่มีตัวสร้างเริ่มต้นการกำหนดค่าเริ่มต้นจะดำเนินการ
  • มิฉะนั้นหาก T เป็นประเภทการรวมการเริ่มต้นแบบรวมจะดำเนินการ
  • มิฉะนั้นถ้า T เป็นความเชี่ยวชาญเฉพาะของ std :: initializer_list อ็อบเจ็กต์ T จะถูกกำหนดค่าเริ่มต้นโดยตรงหรือ copy-initialized ขึ้นอยู่กับบริบทจาก braced-init-list
  • มิฉะนั้นจะพิจารณาตัวสร้างของ T ในสองขั้นตอน:

    • ตัวสร้างทั้งหมดที่ใช้ std :: initializer_list เป็นอาร์กิวเมนต์เดียวหรือเป็นอาร์กิวเมนต์แรกหากอาร์กิวเมนต์ที่เหลือมีค่าดีฟอลต์จะถูกตรวจสอบและจับคู่โดยการแก้ปัญหาโอเวอร์โหลดกับอาร์กิวเมนต์เดียวประเภท std :: initializer_list
    • หากขั้นตอนก่อนหน้าไม่เกิดการจับคู่ตัวสร้างทั้งหมดของ T จะมีส่วนร่วมในการแก้ปัญหาโอเวอร์โหลดกับชุดของอาร์กิวเมนต์ที่ประกอบด้วยองค์ประกอบของ braced-init-list โดยมีข้อ จำกัด ที่อนุญาตให้ใช้เฉพาะการแปลงที่ไม่ จำกัด เท่านั้น หากขั้นตอนนี้สร้างตัวสร้างที่ชัดเจนเป็นตัวสร้างที่เหมาะสมที่สุดสำหรับการเริ่มต้นรายการคัดลอกการคอมไพล์จะล้มเหลว (หมายเหตุในการเริ่มต้นการคัดลอกอย่างง่ายจะไม่พิจารณาตัวสร้างที่ชัดเจนเลย)
  • มิฉะนั้น (ถ้า T ไม่ใช่ประเภทคลาส) หาก braced-init-list มีเพียงองค์ประกอบเดียวและ T ไม่ใช่ประเภทอ้างอิงหรือเป็นประเภทอ้างอิงที่เข้ากันได้กับประเภทขององค์ประกอบ T จะตรง - initialized (in direct-list-initialization) หรือ copy-initialized (in copy-list-initialization) ยกเว้นว่าไม่อนุญาตให้ จำกัด การแปลงให้แคบลง

  • มิฉะนั้นหาก T เป็นประเภทการอ้างอิงที่ไม่เข้ากันกับประเภทขององค์ประกอบ (สิ่งนี้จะล้มเหลวหากการอ้างอิงเป็นการอ้างอิง lvalue ที่ไม่ใช่ const)
  • มิฉะนั้นถ้า braced-init-list ไม่มีองค์ประกอบ T คือค่าเริ่มต้น

ก่อน C ++ 11 สำหรับฟังก์ชันที่ส่งคืน a std::stringคุณจะต้องเขียน:

std::string get_string() {
    return std::string();
}

การใช้ไวยากรณ์รั้งใน C ++ 11 คุณไม่จำเป็นต้องทำซ้ำประเภท:

std::string get_string() {
    return {}; // an empty string is returned
}

return NULLและreturn nullptrควรใช้เมื่อฟังก์ชันส่งคืนประเภทตัวชี้:

any_type* get_pointer() {
    return nullptr;
}

อย่างไรก็ตามNULLเลิกใช้งานตั้งแต่ C ++ 11 เนื่องจากเป็นเพียงนามแฝงของค่าจำนวนเต็ม (0) ในขณะที่nullptrเป็นประเภทตัวชี้จริง:

int get_int() {
    return NULL; // will compile, NULL is an integer
}

int get_int() {
    return nullptr; // error: nullptr is not an integer
}

91

สิ่งนี้อาจสับสน:

int foo()
{
  return {};   // honestly, just return 0 - it's clearer
}

นี่อาจไม่ใช่:

SomeObjectWithADefaultConstructor foo()
{
  return {};
  // equivalent to return SomeObjectWithADefaultConstructor {};
}

9
ดังนั้นจึงเป็นข้อผิดพลาดเวลาคอมไพล์หากประเภทการส่งคืนไม่มีตัวสร้างเริ่มต้นถูกต้อง?
เดีย

10
เป็นข้อผิดพลาดในการคอมไพล์หากประเภทการส่งคืนเป็นคลาสที่ไม่มีตัวสร้างเริ่มต้นที่ไม่ชัดเจนและไม่ใช่การรวม
Oktalist

3
หากประเภทมีตัวinitializer_listสร้างจะไม่ใช้หากไม่มีตัวสร้างเริ่มต้น
celtschk

4
"คงสับสน"? นี่คือเหตุผลที่วิญญาณที่ไม่มีชื่อบางคนเรียกว่า "ลามกอนาจารนั่นคือ C ++"? การบันทึกใด ๆ ก็ตามในการกดแป้นพิมพ์สิ่งนี้สามารถพิสูจน์ได้ว่าอาจมีการขาดความชัดเจนหรือไม่? นี่เป็นคำถามที่จริงใจ โปรดโน้มน้าวใจฉันด้วยตัวอย่างที่ใช้ได้จริง
MickeyfAgain_BeforeExitOfSO

4
return {}ไม่เทียบเท่าreturn SomeObjectWithADefaultConstructor{};
MM

26

return {};หมายความว่า{}เป็นค่าเริ่มต้นสำหรับค่าตอบแทน ค่าที่ส่งคืนเป็นรายการเริ่มต้นด้วยรายการว่าง


นี่คือพื้นหลังบางส่วนของค่าส่งคืนโดยอ้างอิงจาก [stmt.return] ในมาตรฐาน C ++:

สำหรับฟังก์ชั่นที่ให้ผลตอบแทนตามมูลค่า (เช่นพิมพ์กลับไม่ใช่การอ้างอิงและไม่ได้void) มีวัตถุชั่วคราวที่เรียกว่าค่าตอบแทน วัตถุนี้สร้างขึ้นโดยreturnคำสั่งและตัวเริ่มต้นขึ้นอยู่กับสิ่งที่อยู่ในคำสั่ง return

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

ค่าที่ส่งคืนสามารถเริ่มต้นได้สองวิธี:


สมมติว่าTเป็นประเภทการส่งคืนของฟังก์ชันจากนั้นสังเกตว่าreturn T{};แตกต่างจากreturn {}: ในอดีตชั่วคราวT{}จะถูกสร้างขึ้นจากนั้นค่าที่ส่งคืนจะถูกคัดลอกเริ่มต้นจากชั่วคราวนั้น

สิ่งนี้จะล้มเหลวในการคอมไพล์หากTไม่มี copy / move-constructor ที่สามารถเข้าถึงได้ แต่return {};จะทำได้สำเร็จแม้ว่าจะไม่มีตัวสร้างเหล่านั้นก็ตาม ดังนั้นreturn T{};อาจแสดงผลข้างเคียงของตัวสร้างสำเนา ฯลฯ แม้ว่านี่จะเป็นบริบทการคัดลอกสำเนาดังนั้นจึงอาจไม่ได้


นี่คือสรุปสั้น ๆ เกี่ยวกับการเริ่มต้นรายการใน C ++ 14 (N4140 [dcl.init.list] / 3) โดยที่ initializer เป็นรายการว่าง:

  • ถ้าTเป็นการรวมสมาชิกแต่ละคนจะเริ่มต้นจากวงเล็บปีกกาหรือเท่ากับค่าเริ่มต้นหากมีหรือ{} ใช้ขั้นตอนเหล่านี้ซ้ำ
  • ถ้าTเป็นประเภทคลาสที่มีคอนสตรัคเตอร์ดีฟอลต์ที่ผู้ใช้ระบุคอนสตรัคเตอร์นั้นจะถูกเรียก
  • ถ้าTเป็นประเภทคลาสที่มีคอน= defaultสตรัคเตอร์ที่กำหนดโดยนัยหรือed ดีฟอลต์อ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้นเป็นศูนย์จากนั้นคอนสตรัคเตอร์เริ่มต้นจะถูกเรียกใช้
  • ถ้าTเป็น a std::initializer_listค่าที่ส่งคืนจะเป็นรายการว่างเปล่า
  • มิฉะนั้น (กล่าวTคือเป็นประเภทที่ไม่ใช่คลาส - ชนิดที่ส่งคืนไม่สามารถเป็นอาร์เรย์ได้) ค่าที่ส่งคืนจะเริ่มต้นเป็นศูนย์

Aggregate init มาก่อนและจะเริ่มต้นสมาชิกทุกคนแบบวนซ้ำ{}ซึ่งอาจเป็นค่าเริ่มต้นหรือไม่ก็ได้
TC

@TC ถูกต้องฉันใช้ cppreference แต่มองข้าม "จนถึง C ++ 14"
MM

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