'ใหม่' และ 'ลบ' ได้รับการเลิกใช้ใน C ++ หรือไม่


68

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

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

อย่างไรก็ตามฉันเห็นว่าหนึ่งในวิธีแก้ปัญหาอนุญาตกรณีดังต่อไปนี้:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

หลังจากการวิจัยเล็กน้อยฉันอ่านว่า g ++ อนุญาตให้ทำสิ่งนี้ได้ แต่มันทำให้ฉันนึกถึงซึ่งในกรณีนี้จำเป็นต้องใช้การจัดสรรแบบไดนามิกหรือไม่ หรือว่าคอมไพเลอร์แปลว่าเป็นการจัดสรรแบบไดนามิกหรือไม่

ฟังก์ชั่นลบรวมอยู่ด้วย อย่างไรก็ตามโปรดทราบว่าคำถามในที่นี้ไม่เกี่ยวกับการรั่วไหลของหน่วยความจำ


54
ตัวอย่างที่สองใช้อาร์เรย์ความยาวผันแปรซึ่งไม่เคยเป็นส่วนหนึ่งของ C ++ สำหรับกรณีนี้ใช้std::vectorแทน ( std::vector<int> array(N);)
โปรแกรมเมอร์บางคนเพื่อน

7
คำตอบที่ตรงกับคำถามของคุณควรเป็น: ไม่ไม่ได้รับการคัดค้าน แม้ว่า C ++ รุ่นทันสมัยจะมีคุณสมบัติมากมายที่ทำให้การจัดการความเป็นเจ้าของหน่วยความจำง่ายขึ้น (สมาร์ทพอยน์เตอร์) แต่ก็ยังคงเป็นเรื่องธรรมดาที่จะจัดสรรวัตถุโดยเรียกใช้new OBJโดยตรง
pptaszni

8
สำหรับคนอื่นที่สับสนเกี่ยวกับสาเหตุที่ผู้คนกำลังพูดถึงการรั่วไหลของความจำคำถามถูกแก้ไขเพื่อแก้ไขข้อบกพร่องที่ไม่เป็นสาระสำคัญสำหรับคำถาม
Mike Caron

4
@Manoj ต้องการใช้เงื่อนไข Dynamic และ Automatic เพื่อฮีปและสแต็ก เป็นของหายาก แต่ก็เป็นไปได้ที่จะใช้ C ++ โดยไม่ต้องกองและกอง
4581301

1
ไม่เคยมีการคัดค้านใน C ++ และจะไม่มีอะไรเกิดขึ้น นั่นเป็นส่วนหนึ่งของความหมายของ C ++
JoelFan

คำตอบ:


114

ตัวอย่างข้อมูลที่คุณแสดงเป็นรหัส C ++ ที่ทันสมัย

newและdelete(และnew[]และdelete[]) จะไม่คัดค้านใน C ++ และจะไม่เป็น พวกเขายังคงเป็นวิธีที่จะยกตัวอย่างวัตถุที่จัดสรรแบบไดนามิก อย่างไรก็ตามในขณะที่คุณต้องจับคู่newกับdelete(และnew[]a delete[]) เสมอพวกเขาจะถูกเก็บไว้ในชั้นเรียน (ห้องสมุด) ที่ดีที่สุดสำหรับคุณ ดูเหตุใดโปรแกรมเมอร์ C ++ ควรลดการใช้ 'ใหม่' ให้น้อยที่สุด .

ตัวอย่างแรกของคุณใช้ "เปล่า" new[]แล้วไม่เคยdelete[]เป็นอาร์เรย์ที่สร้างขึ้น นั่นเป็นปัญหา std::vectorทำทุกสิ่งที่คุณต้องการที่นี่ได้ดี มันจะใช้รูปแบบของnewเบื้องหลัง (ฉันจะไม่ดำดิ่งลงไปในรายละเอียดการนำไปใช้) แต่สำหรับทุกคนที่คุณต้องใส่ใจมันเป็นอาเรย์แบบไดนามิก แต่ดีกว่าและปลอดภัยกว่า

ตัวอย่างที่สองของคุณใช้ "อาร์เรย์ความยาวผันแปร" (VLAs) ซึ่งเป็นคุณลักษณะ C ที่บางตัวใช้คอมไพเลอร์อนุญาตให้ใช้ใน C ++ เป็นส่วนขยาย ซึ่งแตกต่างจากnewVLAs เป็นหลักปันส่วนในกอง (ทรัพยากรที่ จำกัด มาก) แต่ที่สำคัญกว่านั้นคือพวกเขาไม่ใช่คุณสมบัติ C ++ มาตรฐานและควรหลีกเลี่ยงเพราะไม่ได้พกพา แน่นอนว่าพวกเขาจะไม่แทนที่การจัดสรรแบบไดนามิก (เช่นฮีป)


3
ฉันต้องการเพิ่มว่าถึงแม้ว่า VLAs จะไม่ได้อยู่ในมาตรฐานอย่างเป็นทางการแต่ทว่ามันได้รับการสนับสนุนจากผู้รวบรวมรายใหญ่ทั้งหมดและการตัดสินใจว่าจะหลีกเลี่ยงพวกเขานั้นเป็นเรื่องของสไตล์ / ความนิยมมากกว่าความกังวลเรื่องการพกพา
Stack Tracer

4
โปรดจำไว้ว่าคุณไม่สามารถส่งคืนอาร์เรย์หรือเก็บไว้ที่อื่นได้ดังนั้น VLA จะไม่อยู่ได้นานกว่าเวลาประมวลผลของฟังก์ชัน
Ruslan

16
@StackTracer ที่สุดของความรู้ของฉัน MSVC ไม่สนับสนุน VLAs และ MSVC ก็เป็น "ผู้รวบรวมหลัก" อย่างแน่นอน
Max Langhof

2
"คุณต้องจับคู่ใหม่กับการลบเสมอ" - ไม่ใช่ถ้าคุณทำงานด้วยQtเนื่องจากคลาสพื้นฐานของมันมีตัวรวบรวมขยะดังนั้นคุณจึงใช้newและลืมมันเกือบตลอดเวลา สำหรับองค์ประกอบ GUI เมื่อวิดเจ็ตพาเรนต์ถูกปิดเด็ก ๆ ไม่อยู่ในขอบเขตและรวบรวมขยะโดยอัตโนมัติ
vsz

6
@vsz แม้ใน Qt แต่ละnewยังคงมีการจับคู่delete; เป็นเพียงว่าdeletes ถูกทำโดยวิดเจ็ตหลักมากกว่าในบล็อกรหัสเดียวกันกับnews
jjramsey

22

ดีสำหรับการเริ่มnew/ deleteไม่ได้รับการคัดค้าน

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

ตัวอย่างที่ 2 ของคุณใช้ส่วนขยาย VLA ที่ไม่ได้มาตรฐานซึ่งพยายามปรับให้เหมาะสมกับอาร์เรย์บนสแต็ก นี่มีข้อ จำกัด บางประการ - คือขนาดที่ จำกัด และไม่สามารถใช้หน่วยความจำนี้หลังจากที่อาร์เรย์ไม่อยู่ในขอบเขต คุณไม่สามารถย้ายออกได้มันจะ "หายไป" หลังจากกองซ้อนคลายตัว

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

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


1
หมายเหตุ "... ไม่กี่ชั้นลึกกว่า" ถ้าคุณจะใช้ภาชนะของคุณเองคุณยังควรหลีกเลี่ยงการใช้newและแต่ใช้ตัวชี้สมาร์ทเช่นdelete std::unique_pointer
สูงสุด

1
ซึ่งจริงๆแล้วเรียกว่าstd::unique_ptr
user253751

2
@Max: std::unique_ptrการโทรเริ่มต้นของ destructor deleteหรือdelete[]ซึ่งหมายความว่าวัตถุที่เป็นเจ้าของต้องได้รับการจัดสรรโดยnewหรือnew[]อย่างไรก็ตามการโทรใดที่ถูกซ่อนไว้std::make_uniqueตั้งแต่ C ++ 14
Laurent LA RIZZA

15

ตัวอย่างที่สองของคุณใช้อาร์เรย์ยาวตัวแปร (Vlas) ซึ่งเป็นจริงC99 ( ไม่ C ++) คุณสมบัติ แต่กระนั้นการสนับสนุนโดยกรัม ++

ดูคำตอบนี้ด้วย

โปรดทราบว่าอาร์เรย์ความยาวผันแปรนั้นแตกต่างจากnew/ deleteและอย่า "เลิกใช้" ในทางใดทางหนึ่ง

โปรดทราบด้วยว่า VLA ไม่ใช่ ISO C ++


13

Modern C ++ มอบวิธีที่ง่ายกว่าในการทำงานกับการจัดสรรแบบไดนามิก ตัวชี้สมาร์ทสามารถดูแลเกี่ยวกับการล้างข้อมูลหลังจากข้อยกเว้น (ซึ่งอาจเกิดขึ้นที่ใดก็ได้หากได้รับอนุญาต) และผลตอบแทนก่อนกำหนดทันทีที่โครงสร้างข้อมูลอ้างอิงนอกขอบเขตดังนั้นอาจเหมาะสมที่จะใช้สิ่งเหล่านี้แทน:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

จาก C ++ 14 คุณยังสามารถเขียนได้

auto buffer_new = std::make_unique<int[]>(size);

สิ่งนี้ดูดีกว่าและจะป้องกันการรั่วไหลของหน่วยความจำหากการจัดสรรล้มเหลว จาก C ++ 20 คุณน่าจะทำได้มาก

auto a = std::make_shared<int[]>(size);

สิ่งนี้สำหรับฉันยังไม่ได้รวบรวมในขณะที่เขียนด้วย gcc 7.4.0 ในตัวอย่างทั้งสองนี้เรายังใช้autoแทนการประกาศประเภททางด้านซ้าย ในทุกกรณีใช้อาร์เรย์ตามปกติ:

buffer_old[0] = buffer_new[0] = 17;

การรั่วไหลของหน่วยความจำจากnewและความล้มเหลวจากสองเท่าdeleteเป็นสิ่งที่ C ++ ได้รับการทุบตีมานานหลายปีเป็น "จุดกลาง" ของการโต้แย้งเพื่อเปลี่ยนเป็นภาษาอื่น อาจจะดีกว่าที่จะหลีกเลี่ยง


คุณควรหลีกเลี่ยงunique/shared_ptrConstructor make_unique/sharedไม่เพียง แต่คุณไม่จำเป็นต้องเขียนชนิดที่สร้างขึ้นสองครั้ง (โดยใช้auto) แต่คุณไม่ต้องเสี่ยงต่อการรั่วไหลของหน่วยความจำหรือทรัพยากรหากการก่อสร้างล้มเหลวไปบางส่วน (ถ้าคุณใช้ชนิดที่ล้มเหลว)
Simon Buchan

2
make_unique พร้อมใช้งานกับอาร์เรย์จาก C ++ 14 และ make_shared จาก C ++ 20 เท่านั้น นี่ยังไม่ค่อยเป็นการตั้งค่าเริ่มต้นดังนั้นการเสนอ std :: make_shared <int []> (ขนาด) มองหาฉันล่วงหน้า
Audrius Meskauskas

ยุติธรรมพอ! ฉันไม่ค่อยได้ใช้make_shared<int[]>อะไรมากนักเมื่อคุณต้องการเสมอvector<int>แต่ก็น่าจะรู้
Simon Buchan

pedantry มากเกินไป แต่ IIRC unique_ptrpedantryคอนสตรัคเตอร์ไม่ได้รับการตัดดังนั้นจึงTไม่มีคอนสตรัคเตอร์ดังนั้นจึงไม่มีความเสี่ยงต่อการรั่วไหลด้วยunique_ptr(new int[size])และshared_ptrมีสิ่งต่อไปนี้: "ถ้าข้อยกเว้นถูกโยนทิ้งจะถูกเรียกเมื่อ T ไม่ใช่ประเภทอาเรย์ ] หน้ามิฉะนั้น "เพื่อให้คุณมีผลเช่นเดียวกัน. - unique/shared_ptr(new MyPossiblyAllocatingType[size])ความเสี่ยงสำหรับ
Simon Buchan

3

ใหม่และการลบจะไม่ได้รับการคัดค้าน

วัตถุที่สร้างโดยผู้ประกอบการใหม่สามารถส่งผ่านโดยการอ้างอิง วัตถุสามารถลบได้โดยใช้การลบ

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

คำสั่ง - int array [N]เป็นวิธีการกำหนดอาร์เรย์ อาร์เรย์สามารถใช้ภายในขอบเขตของการปิดกั้นรหัส ไม่สามารถส่งผ่านได้เหมือนวิธีที่วัตถุถูกส่งผ่านไปยังฟังก์ชันอื่น


2

ตัวอย่างแรกต้องใช้delete[]ในตอนท้ายมิฉะนั้นคุณจะมีหน่วยความจำรั่ว

ตัวอย่างที่สองใช้ความยาวของอาเรย์ตัวแปรที่ C ++ ไม่รองรับ มันเพียง แต่ช่วยให้คงที่การแสดงออกสำหรับความยาวอาร์เรย์

ในกรณีนี้มันมีประโยชน์ที่จะใช้std::vector<>เป็นวิธีแก้ปัญหา; ที่ล้อมรอบการกระทำทั้งหมดที่คุณสามารถทำได้ในอาร์เรย์ลงในคลาสเทมเพลต


3
คุณหมายถึงอะไรกับ "จนถึง C ++ 11" ฉันค่อนข้างแน่ใจว่า VLA ไม่เคยเป็นส่วนหนึ่งของมาตรฐาน
churill

ดูมาตรฐานของ c ++ 14 [c ++ 14 มาตรฐาน] ( isocpp.org/files/papers/N3690.pdf ) ในหน้า 184 วรรค 8.3.4
zig razor

4
ไม่ thats . มาตรฐาน แต่เพียงร่างและเป็นส่วนหนึ่งเกี่ยวกับ“อาร์เรย์รันไทม์ผูกพัน" ไม่ได้ทำให้มันกลายเป็นมาตรฐานเท่าที่ผมสามารถบอกcppreferenceไม่ได้พูดถึง Vlas มี.
churill

1
@zigrazor cppreference.com มีรายการลิงก์ไปยังร่างที่ใกล้ที่สุดก่อน / หลังการประกาศของแต่ละมาตรฐาน มาตรฐานที่เผยแพร่ไม่สามารถใช้ได้อย่างอิสระ แต่ร่างเหล่านี้ควรอยู่ใกล้มาก ดังที่คุณเห็นได้จากหมายเลขเอกสารร่างที่เชื่อมโยงของคุณเป็นแบบร่างที่ทำงานเก่ากว่าสำหรับ C ++ 14
วอลนัท

2
@learning_dude ไม่รองรับมาตรฐาน คำตอบคือ (ตอนนี้) ถูกต้อง (แม้ว่าสั้น) ใช้งานได้กับคุณเท่านั้นเนื่องจาก GCC อนุญาตให้เป็นส่วนขยายที่ไม่ได้มาตรฐาน
วอลนัท

-4

ไวยากรณ์ดูเหมือน C ++ แต่สำนวนนั้นคล้ายกับ Algol60 แบบเก่า เป็นเรื่องปกติที่มีบล็อคโค้ดดังนี้:

read n;
begin
    integer array x[1:n];
    ... 
end;

ตัวอย่างสามารถเขียนเป็น:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

บางครั้งฉันคิดถึงสิ่งนี้ในภาษาปัจจุบัน;)

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