เหตุใดโปรแกรมเมอร์ C ++ ควรลดการใช้ 'ใหม่' ให้น้อยที่สุด


873

ฉันสะดุดกับคำถามStack Overflow หน่วยความจำรั่วด้วย std :: string เมื่อใช้ std :: list <std :: string>และหนึ่งในความเห็นบอกว่า:

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

ฉันไม่แน่ใจจริงๆว่าเขาหมายถึงอะไร

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

คำตอบ:


1037

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

ซ้อนกัน

สแต็กจะจัดสรรหน่วยความจำตามลำดับเสมอ สามารถทำได้เนื่องจากต้องการให้คุณปล่อยหน่วยความจำในลำดับย้อนกลับ (First-In, Last-Out: FILO) นี่เป็นเทคนิคการจัดสรรหน่วยความจำสำหรับตัวแปรโลคัลในภาษาการเขียนโปรแกรมจำนวนมาก มันเร็วมากเพราะต้องมีการทำบัญชีน้อยที่สุดและที่อยู่ถัดไปในการจัดสรรคือโดยปริยาย

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

กอง

ฮีปช่วยให้โหมดการจัดสรรหน่วยความจำมีความยืดหยุ่นมากขึ้น การทำบัญชีมีความซับซ้อนมากขึ้นและการจัดสรรช้าลง เนื่องจากไม่มีจุดปล่อยโดยปริยายคุณจะต้องปล่อยหน่วยความจำด้วยตนเองโดยใช้deleteหรือdelete[]( freeใน C) อย่างไรก็ตามการไม่มีจุดปล่อยโดยปริยายเป็นกุญแจสำคัญในความยืดหยุ่นของกอง

เหตุผลที่ใช้การจัดสรรแบบไดนามิก

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

เหตุผลสำคัญสองข้อในการใช้การจัดสรรแบบไดนามิก:

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

  • คุณต้องการจัดสรรหน่วยความจำซึ่งจะคงอยู่หลังจากออกจากบล็อกปัจจุบัน ตัวอย่างเช่นคุณอาจต้องการเขียนฟังก์ชั่นstring readfile(string path)ที่คืนค่าเนื้อหาของไฟล์ ในกรณีนี้แม้ว่าสแต็คสามารถเก็บเนื้อหาไฟล์ทั้งหมดคุณไม่สามารถกลับมาจากฟังก์ชั่นและเก็บบล็อกหน่วยความจำที่จัดสรร

ทำไมการจัดสรรแบบไดนามิกมักไม่จำเป็น

ใน C ++ มีโครงสร้างเรียบร้อยเรียกว่าdestructor กลไกนี้ช่วยให้คุณสามารถจัดการทรัพยากรโดยการปรับอายุการใช้งานของทรัพยากรให้สอดคล้องกับอายุการใช้งานของตัวแปร เทคนิคนี้เรียกว่าRAIIและเป็นจุดแยกของ C ++ มัน "ห่อ" ทรัพยากรลงในวัตถุ std::stringเป็นตัวอย่างที่สมบูรณ์แบบ ตัวอย่างนี้:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

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

โดยเฉพาะอย่างยิ่งมันบอกเป็นนัยว่าในตัวอย่างนี้:

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

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

ทำไมคุณควรใช้ที่เก็บข้อมูลอัตโนมัติบ่อยที่สุดเท่าที่จะเป็นไปได้

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

  • พิมพ์เร็วขึ้น
  • เร็วกว่าเมื่อวิ่ง
  • การรั่วไหลของหน่วยความจำ / ทรัพยากรน้อย

คะแนนโบนัส

ในคำถามอ้างอิงมีข้อสงสัยเพิ่มเติม โดยเฉพาะคลาสต่อไปนี้:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

จริง ๆ แล้วมีความเสี่ยงที่จะใช้มากกว่าหนึ่งต่อไปนี้:

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

เหตุผลก็คือstd::stringกำหนดตัวสร้างสำเนาอย่างถูกต้อง พิจารณาโปรแกรมต่อไปนี้:

int main ()
{
    Line l1;
    Line l2 = l1;
}

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

บันทึกอื่น ๆ

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

หากคุณใช้Lineคลาสเป็นแบบเอกสารสำเร็จรูป:

 class Table
 {
      Line borders[4];
 };

แล้วก็

 int main ()
 {
     Table table;
 }

จัดสรรสี่std::stringกรณีสี่Lineกรณีหนึ่งTableอินสแตนซ์และทุกเนื้อหาสตริงและทุกอย่างเป็นอิสระโดยอัตโนมัติ


55
+1 เมื่อพูดถึง RAII ในตอนท้าย แต่ควรมีบางอย่างเกี่ยวกับข้อยกเว้นและคลี่คลายคลี่คลาย
Tobu

7
@Tobu: ใช่ แต่โพสต์นี้ค่อนข้างยาวและฉันต้องการให้มันค่อนข้างมุ่งเน้นไปที่คำถามของ OP ฉันจะจบลงด้วยการเขียนโพสต์บล็อกหรืออะไรและเชื่อมโยงไปจากที่นี่
André Caron

15
จะเป็นอาหารเสริมที่ดีที่จะกล่าวถึงข้อเสียสำหรับการจัดสรรสแต็ก (อย่างน้อยจนถึง C ++ 1x) - คุณมักจะต้องคัดลอกสิ่งต่าง ๆ โดยไม่จำเป็นถ้าคุณไม่ระวัง เช่นMonsterถ่มน้ำลายออกTreasureไปWorldเมื่อมันตาย ในDie()วิธีการของมันจะเพิ่มสมบัติให้กับโลก มันจะต้องใช้world->Add(new Treasure(/*...*/))ในการอื่นเพื่อรักษาสมบัติหลังจากที่มันตาย ทางเลือกคือshared_ptr(อาจมีมากเกินไป), auto_ptr(ความหมายไม่ดีสำหรับการโอนกรรมสิทธิ์), ผ่านตามค่า (สิ้นเปลือง) และmove+ unique_ptr(ยังไม่ได้นำไปใช้อย่างกว้างขวาง)
kizzx2

7
สิ่งที่คุณพูดเกี่ยวกับตัวแปรโลคัลที่จัดสรรโดยสแต็กอาจทำให้เข้าใจผิดเล็กน้อย "สแต็ก" หมายถึงการโทรสแต็คซึ่งเก็บเฟรมสแต็ก มันเป็นสแต็กเฟรมที่จัดเก็บในรูปแบบ LIFO ตัวแปรโลคัลสำหรับเฟรมเฉพาะถูกจัดสรรราวกับว่าพวกเขาเป็นสมาชิกของ struct
someguy

7
@someguy: แน่นอนคำอธิบายไม่สมบูรณ์ การดำเนินการมีเสรีภาพในนโยบายการจัดสรร อย่างไรก็ตามตัวแปรจะต้องเริ่มต้นและทำลายในรูปแบบ LIFO ดังนั้นการเปรียบเทียบจึงถือ ฉันไม่คิดว่ามันจะทำให้คำตอบซับซ้อนขึ้นอีก
André Caron

171

เพราะสแต็คเร็วและกันการรั่ว

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


18
"ต้องใช้คำสั่งเดียวในการจัดสรรพื้นที่" - โอ้ไร้สาระ แน่ใจว่าใช้คำสั่งเดียวเท่านั้นในการเพิ่มไปยังตัวชี้สแต็ก แต่ถ้าคลาสมีโครงสร้างภายในที่น่าสนใจจะมีจำนวนมากกว่าการเพิ่มไปยังตัวชี้สแต็คที่เกิดขึ้น มีความถูกต้องพอ ๆ กันที่จะบอกว่าใน Java นั้นไม่มีคำแนะนำในการจัดสรรพื้นที่เนื่องจากคอมไพเลอร์จะจัดการการอ้างอิงในเวลารวบรวม
Charlie Martin

31
@ Charlie ถูกต้อง ตัวแปรอัตโนมัตินั้นเร็วและเข้าใจผิดจะแม่นยำยิ่งขึ้น
Oliver Charlesworth

28
@Charlie: internals ชั้นต้องตั้งค่าอย่างใดอย่างหนึ่ง มีการเปรียบเทียบการจัดสรรพื้นที่ที่ต้องการ
Oliver Charlesworth

51
กระแอม int x; return &x;
peterchen

16
เร็วใช่ แต่ก็ไม่อาจป้องกันได้อย่างแน่นอน ไม่มีอะไรจะเข้าใจผิดได้ คุณสามารถรับ StackOverflow :)
rxantos

107

สาเหตุที่ซับซ้อน

ก่อนอื่น C ++ จะไม่เก็บขยะ ดังนั้นสำหรับทุก ๆ ใหม่จะต้องมีการลบที่สอดคล้องกัน หากคุณไม่ได้ใส่การลบนี้แสดงว่าคุณมีหน่วยความจำรั่ว ตอนนี้สำหรับกรณีง่าย ๆ เช่นนี้:

std::string *someString = new std::string(...);
//Do stuff
delete someString;

มันง่ายมาก แต่จะเกิดอะไรขึ้นถ้า "สิ่งที่ทำ" ส่งข้อยกเว้น? อ๊ะ: หน่วยความจำรั่ว จะเกิดอะไรขึ้นถ้าปัญหา "สิ่งที่ทำ" returnเร็วขึ้น อ๊ะ: หน่วยความจำรั่ว

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

หรือคุณสามารถทำสิ่งนี้:

std::string someString(...);
//Do stuff

deleteไม่ วัตถุถูกสร้างขึ้นบน "สแต็ก" และมันจะถูกทำลายเมื่อมันออกนอกขอบเขต คุณสามารถคืนค่าวัตถุได้ดังนั้นจึงถ่ายโอนเนื้อหาไปยังฟังก์ชันการโทร คุณสามารถส่งผ่านวัตถุไปยังฟังก์ชั่น (โดยทั่วไปจะเป็นการอ้างอิงหรืออ้างอิงแบบอ้างอิง: void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)เป็นต้น

ทั้งหมดโดยไม่ต้องและnew deleteไม่มีคำถามว่าใครเป็นเจ้าของหน่วยความจำหรือผู้รับผิดชอบในการลบ ถ้าคุณทำ:

std::string someString(...);
std::string otherString;
otherString = someString;

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

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

เห็นความคิดหรือไม่


1
ในบันทึกย่อนั้น ... หากมีการจัดสรรวัตถุแบบไดนามิกในmain()ช่วงระยะเวลาของโปรแกรมจะไม่สามารถสร้างได้อย่างง่ายดายบนสแต็กเนื่องจากสถานการณ์และตัวชี้ไปยังวัตถุจะถูกส่งผ่านไปยังฟังก์ชันใด ๆ ที่จำเป็นต้องเข้าถึง สิ่งนี้สามารถทำให้เกิดการรั่วไหลในกรณีที่โปรแกรมขัดข้องหรือปลอดภัยหรือไม่ ฉันจะถือว่าหลังตั้งแต่ระบบปฏิบัติการ deallocating ทั้งหมดของหน่วยความจำของโปรแกรมเหตุผลควร deallocate มันเกินไป newแต่ฉันไม่ต้องการที่จะคิดอะไรเมื่อมันมาถึง
Justin Time - Reinstate Monica

4
@JustinTime คุณไม่ต้องกังวลกับการเพิ่มหน่วยความจำของออบเจ็กต์ที่จัดสรรแบบไดนามิกซึ่งจะคงอยู่ตลอดอายุการใช้งานของโปรแกรม เมื่อโปรแกรมทำงานระบบปฏิบัติการจะสร้างแผนที่ของหน่วยความจำกายภาพหรือหน่วยความจำเสมือนจริง ทุกที่อยู่ในพื้นที่หน่วยความจำเสมือนจะถูกแมปไปยังที่อยู่ของหน่วยความจำกายภาพและเมื่อโปรแกรมออกไปสิ่งที่แมปกับหน่วยความจำเสมือนทั้งหมดจะถูกปล่อยให้เป็นอิสระ ดังนั้นตราบใดที่โปรแกรมออกไปอย่างสมบูรณ์คุณไม่จำเป็นต้องกังวลว่าหน่วยความจำที่จัดสรรไว้จะไม่ถูกลบ
Aiman ​​Al-Eryani

75

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

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

ชี้สมาร์ทชอบunique_ptr, shared_ptrการแก้ปัญหาการอ้างอิงห้อย แต่พวกเขาจำเป็นต้องมีการเข้ารหัสระเบียบวินัยและมีปัญหาอื่น ๆ ที่มีศักยภาพ (copyability ห่วงอ้างอิงอื่น ๆ )

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

ข้อเสียของวัตถุที่มีค่าคือพวกเขาตายเมื่อฟังก์ชั่นโฮสต์ส่งคืน - คุณไม่สามารถส่งต่อการอ้างอิงกลับไปยังผู้โทรได้เพียงแค่คัดลอกกลับหรือย้ายตามค่า


9
+1 เรื่อง "วัตถุที่สร้างขึ้นโดยnewจะต้องในที่สุดdeleteเพื่อมิให้มันรั่ว" - ยิ่งแย่ไปกว่านั้นnew[]ต้องจับคู่delete[]และคุณจะมีพฤติกรรมที่ไม่ได้กำหนดถ้าคุณdelete new[]จำหน่วยความจำหรือdelete[] newหน่วยความจำ - มีคอมไพเลอร์น้อยมากที่เตือนเกี่ยวกับเรื่องนี้ (เครื่องมือบางอย่างเช่น Cppcheck ทำเมื่อทำได้)
Tony Delroy

3
@TonyDelroy มีบางสถานการณ์ที่คอมไพเลอร์ไม่สามารถเตือนได้ ถ้าฟังก์ชั่นส่งคืนพอยน์เตอร์มันจะถูกสร้างขึ้นถ้าใหม่ (องค์ประกอบเดียว) หรือใหม่ []
fbafelipe

32
  • C ++ ไม่ได้จ้างผู้จัดการหน่วยความจำด้วยตัวเอง ภาษาอื่นเช่น C #, Java มีตัวรวบรวมขยะเพื่อจัดการหน่วยความจำ
  • การใช้งาน C ++ โดยทั่วไปจะใช้รูทีนระบบปฏิบัติการเพื่อจัดสรรหน่วยความจำและการลบ / ใหม่ที่มากเกินไปอาจทำให้ส่วนของหน่วยความจำที่มีอยู่
  • สำหรับแอปพลิเคชันใด ๆ หากมีการใช้งานหน่วยความจำบ่อยแนะนำให้จัดสรรล่วงหน้าและปล่อยเมื่อไม่ต้องการใช้
  • การจัดการหน่วยความจำที่ไม่เหมาะสมอาจทำให้หน่วยความจำรั่วและยากต่อการติดตาม ดังนั้นการใช้วัตถุสแต็กภายในขอบเขตของฟังก์ชั่นเป็นเทคนิคที่พิสูจน์แล้ว
  • ข้อเสียของการใช้วัตถุสแต็กคือมันสร้างหลายสำเนาของวัตถุที่กลับมาส่งผ่านไปยังฟังก์ชั่นอื่น ๆ อย่างไรก็ตามคอมไพเลอร์สมาร์ทตระหนักดีถึงสถานการณ์เหล่านี้และพวกเขาได้รับการปรับให้เหมาะสมสำหรับประสิทธิภาพ
  • มันน่าเบื่อจริง ๆ ใน C ++ ถ้าหน่วยความจำถูกจัดสรรและปล่อยในสองแห่ง ความรับผิดชอบสำหรับการเปิดตัวเป็นคำถามเสมอและส่วนใหญ่เราพึ่งพาพอยน์เตอร์ที่สามารถเข้าถึงได้ทั่วไปบางส่วน, วัตถุสแต็ก (มากที่สุด) และเทคนิคเช่น auto_ptr (วัตถุ RAII)
  • สิ่งที่ดีที่สุดคือคุณสามารถควบคุมหน่วยความจำได้และสิ่งที่แย่ที่สุดคือคุณจะไม่สามารถควบคุมหน่วยความจำได้หากเราใช้การจัดการหน่วยความจำที่ไม่เหมาะสมสำหรับแอปพลิเคชัน ความขัดข้องที่เกิดจากการเสียหายของหน่วยความจำเป็นสิ่งที่น่ารังเกียจและยากต่อการติดตาม

5
จริง ๆ แล้วภาษาใด ๆ ที่จัดสรรหน่วยความจำจะมีตัวจัดการหน่วยความจำรวมถึง c ส่วนใหญ่เป็นเรื่องง่ายมากเช่น int * x = malloc (4); int * y = malloc (4); ... การโทรครั้งแรกจะจัดสรรหน่วยความจำหรือที่เรียกว่าระบบปฏิบัติการสำหรับหน่วยความจำ (โดยปกติจะอยู่ในกลุ่ม 1k / 4k) เพื่อให้สายที่สองไม่ได้จัดสรรหน่วยความจำจริง ๆ แต่ให้ชิ้นส่วนสุดท้ายที่จัดสรรให้ IMO ผู้รวบรวมขยะไม่ใช่ผู้จัดการหน่วยความจำเพราะจัดการการจัดสรรคืนหน่วยความจำโดยอัตโนมัติเท่านั้น หากต้องการเรียกใช้ตัวจัดการหน่วยความจำไม่ควรจัดการกับการจัดสรรคืนเท่านั้น แต่ยังจัดสรรหน่วยความจำด้วย
Rahly

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

C ++ ไม่ได้ "ใช้รูทีนระบบปฏิบัติการ"; นั่นไม่ใช่ส่วนหนึ่งของภาษา แต่เป็นเพียงการนำไปใช้ทั่วไป C ++ อาจทำงานโดยไม่มีระบบปฏิบัติการใด ๆ
einpoklum

22

ฉันเห็นว่ามีเหตุผลสำคัญสองสามข้อที่ทำสิ่งใหม่ ๆ ให้พลาด:

ผู้ประกอบการnewมีเวลาดำเนินการที่ไม่ได้กำหนดไว้

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

ตัวดำเนินnewการเป็นการซิงโครไนซ์เธรดโดยนัย

ใช่คุณได้ยินฉันระบบปฏิบัติการของคุณต้องแน่ใจว่าตารางหน้าของคุณสอดคล้องกันและการเรียกเช่นnewนี้จะทำให้เธรดของคุณได้รับการล็อค mutex โดยปริยาย หากคุณโทรnewจากหลาย ๆ กระทู้อย่างต่อเนื่องคุณกำลังจัดลำดับเธรดของคุณ (ฉันได้ทำกับ 32 CPUs แต่ละครั้งที่กดปุ่มnewเพื่อรับสองสามร้อยไบต์แต่ละอัน!

คำตอบอื่น ๆ ที่เหลือเช่นช้าการแตกแฟรกเมนต์เกิดข้อผิดพลาดได้ง่ายเป็นต้น


2
ทั้งสองสามารถหลีกเลี่ยงได้โดยใช้ตำแหน่งใหม่ / ลบและจัดสรรหน่วยความจำก่อนมือ หรือคุณสามารถจัดสรร / เพิ่มหน่วยความจำด้วยตัวคุณเองแล้วเรียก constructor / destructor นี่คือวิธีที่ std :: vector ใช้งานได้ปกติ
rxantos

1
@rxantos โปรดอ่าน OP คำถามนี้เกี่ยวกับการหลีกเลี่ยงการจัดสรรหน่วยความจำที่ไม่จำเป็น นอกจากนี้ยังไม่มีการลบตำแหน่ง
Emily L.

@ Emily นี่คือสิ่งที่ OP หมายถึงฉันคิดว่า:void * someAddress = ...; delete (T*)someAddress
xryl669

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

1
@mikkorantalainen นั้นเป็นความจริงทางเทคนิค แต่ในสถานการณ์ที่หน่วยความจำเหลือน้อยการเดิมพันทั้งหมดจะปิดลงถ้าคุณกดไปที่ดิสก์ดังนั้นจึงไม่มีอะไรที่คุณสามารถทำได้ มันไม่ได้เป็นโมฆะคำแนะนำเพื่อหลีกเลี่ยงการโทรใหม่เมื่อมีเหตุผลที่จะทำ
Emily L.

21

Pre-C ++ 17:

เพราะมันมีแนวโน้มที่จะรั่วไหลที่ลึกซึ้งแม้ว่าคุณจะห่อผลในตัวชี้สมาร์ท

พิจารณาผู้ใช้ "ระวัง" ที่จำการห่อวัตถุในตัวชี้สมาร์ท:

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

รหัสนี้เป็นอันตรายเพราะมีการรับประกันว่าทั้งshared_ptrมีการก่อสร้างก่อนที่ทั้งสองหรือT1 T2ดังนั้นหากหนึ่งnew T1()หรือnew T2()ล้มเหลวหลังจากที่อื่นประสบความสำเร็จแล้ววัตถุแรกจะรั่วไหลออกมาเพราะไม่มีshared_ptrอยู่เพื่อทำลายและยกเลิกการจัดสรรมัน

การแก้ไข: make_sharedการใช้งาน

โพสต์-C ++ 17:

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

คำอธิบายรายละเอียดของการสั่งซื้อการประเมินผลใหม่นำโดย C ++ 17 ถูกจัดให้โดยแบร์รี่ในคำตอบอื่น

ขอขอบคุณ@Remy Lebeauสำหรับการชี้ให้เห็นว่านี่ยังคงเป็นปัญหาภายใต้ C ++ 17 (แม้ว่าจะน้อยกว่านั้น): shared_ptrConstructor สามารถล้มเหลวในการจัดสรรบล็อกควบคุมและการโยนของมัน

การแก้ไข: make_sharedการใช้งาน


5
โซลูชันอื่น ๆ : อย่าจัดสรรวัตถุมากกว่าหนึ่งรายการต่อบรรทัดแบบไดนามิก
พลวง

3
@Antimony: ใช่มันเป็นสิ่งที่ดึงดูดมากขึ้นในการจัดสรรวัตถุมากกว่าหนึ่งวัตถุเมื่อคุณจัดสรรไปแล้วแม้ว่าจะไม่ได้จัดสรรก็ตาม
user541686

1
ฉันคิดว่าคำตอบที่ดีกว่าคือ smart_ptr จะรั่วไหลหากมีการเรียกข้อยกเว้นและไม่มีอะไรที่จับได้
Natalie Adams

2
แม้ในกรณี post-C ++ 17 การรั่วไหลสามารถเกิดขึ้นได้หากnewประสบความสำเร็จและการshared_ptrก่อสร้างที่ตามมาจะล้มเหลว std::make_shared()จะแก้ปัญหานี้ได้เช่นกัน
Remy Lebeau

1
@ Mehrdad คอนสตshared_ptrรัคในคำถามจัดสรรหน่วยความจำสำหรับบล็อกควบคุมที่เก็บตัวชี้ที่ใช้ร่วมกันและ deleter ดังนั้นใช่ในทางทฤษฎีสามารถโยนข้อผิดพลาดของหน่วยความจำ เฉพาะตัวสร้างสำเนาย้ายและสมนามเท่านั้นที่ไม่ได้ใช้ make_sharedจัดสรรวัตถุที่ใช้ร่วมกันภายในบล็อกควบคุมดังนั้นจึงมีการจัดสรรเพียง 1 รายการแทนที่จะเป็น 2
Remy Lebeau

17

ในระดับที่ดีนั่นคือมีคนยกระดับจุดอ่อนของตนเองให้เป็นกฎทั่วไป ไม่มีอะไรที่ผิดปกติต่อ seกับการสร้างวัตถุที่ใช้newประกอบการ สิ่งที่มีข้อโต้แย้งสำหรับคือคุณต้องทำเช่นนั้นกับวินัยบางอย่าง: ถ้าคุณสร้างวัตถุคุณต้องแน่ใจว่ามันจะถูกทำลาย

วิธีที่ง่ายที่สุดในการทำเช่นนั้นคือการสร้างวัตถุในที่จัดเก็บอัตโนมัติดังนั้น C ++ จึงรู้ที่จะทำลายมันเมื่อมันออกนอกขอบเขต:

 {
    File foo = File("foo.dat");

    // do things

 }

ตอนนี้ให้สังเกตว่าเมื่อคุณหลุดออกจากบล็อกนั้นหลังวงเล็บปีกกาfooอยู่นอกขอบเขต C ++ จะเรียก dtor โดยอัตโนมัติให้คุณ แตกต่างจาก Java คุณไม่จำเป็นต้องรอให้ GC ค้นพบ

คุณเขียน

 {
     File * foo = new File("foo.dat");

คุณต้องการจับคู่กับอย่างชัดเจนด้วย

     delete foo;
  }

หรือดีกว่านั้นจัดสรรของคุณFile *เป็น "ตัวชี้สมาร์ท" หากคุณไม่ระวังเรื่องนั้นอาจทำให้เกิดการรั่วไหลได้

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

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

จากนั้นFileImplจะยังคงถูกจัดสรรในสแต็ก

และใช่คุณควรจะมี

     ~File(){ delete fd ; }

ในชั้นเรียนด้วย; หากไม่มีมันคุณจะรั่วไหลหน่วยความจำจากฮีปแม้ว่าคุณจะไม่ได้จัดสรรฮีปเลยก็ตาม


4
คุณควรดูรหัสในคำถามที่อ้างอิง มีหลายสิ่งที่ผิดปกติในรหัสนั้น
André Caron

7
ฉันเห็นด้วยไม่มีอะไรผิดปกติกับการใช้new ต่อ seแต่ถ้าคุณดูรหัสต้นฉบับความคิดเห็นที่ถูกอ้างอิงถึงnewจะถูกทารุณกรรม โค้ดถูกเขียนเหมือนมันคือ Java หรือ C # ซึ่งnewใช้สำหรับทุกตัวแปรในทางปฏิบัติเมื่อสิ่งต่าง ๆ มีความหมายมากขึ้นที่จะอยู่ใน stack
ลูกา

5
จุดยุติธรรม แต่กฎทั่วไปมีการบังคับใช้ตามปกติเพื่อหลีกเลี่ยงข้อผิดพลาดทั่วไป ไม่ว่าจะเป็นจุดอ่อนของแต่ละบุคคลหรือไม่การจัดการหน่วยความจำก็ซับซ้อนพอที่จะรับประกันกฎทั่วไปเช่นนี้! :)
Robben_Ford_Fan_boy

9
@Charlie: แสดงความคิดเห็นไม่ได้newบอกว่าคุณไม่ควรใช้ มันบอกว่าถ้าคุณมีตัวเลือกระหว่างการจัดสรรแบบไดนามิกและที่เก็บข้อมูลอัตโนมัติให้ใช้ที่เก็บข้อมูลอัตโนมัติ
André Caron

8
@ Charlie: ไม่มีอะไรผิดปกติกับการใช้newแต่ถ้าคุณใช้deleteคุณทำผิด!
Matthieu M.

16

new()ไม่ควรใช้น้อยที่สุด ควรใช้อย่างระมัดระวังที่สุด และควรใช้บ่อยเท่าที่จำเป็นตามที่กำหนดโดยลัทธิปฏิบัตินิยม

การจัดสรรวัตถุบนสแต็กอาศัยการทำลายโดยนัยเป็นโมเดลง่าย ๆ หากขอบเขตที่ต้องการของวัตถุนั้นตรงกับโมเดลนั้นก็ไม่จำเป็นต้องใช้new()ด้วยการเชื่อมโยงdelete()และตรวจสอบพอยน์เตอร์พอยน์เตอร์ที่เกี่ยวข้อง ในกรณีที่คุณมีการจัดสรรวัตถุระยะสั้นจำนวนมากบนสแต็กควรลดปัญหาการแตกแฟรกเมนต์ของฮีป

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


9
"ถ้าอายุการใช้งานของวัตถุของคุณต้องการขยายเกินขอบเขตปัจจุบันแล้ว new () คือคำตอบที่ถูกต้อง" ... ทำไมไม่ส่งคืนตามมูลค่าหรือยอมรับตัวแปรที่กำหนดขอบเขตผู้โทรโดยไม่constอ้างอิงหรือชี้ ...
Tony Delroy

2
@ โทนี่: ใช่ใช่! ฉันดีใจที่ได้ยินคนสนับสนุนการอ้างอิง พวกเขาถูกสร้างขึ้นเพื่อป้องกันปัญหานี้
นาธานออสมัน

@TonyD ... หรือรวมเข้าด้วยกัน: ส่งคืนตัวชี้อัจฉริยะตามค่า วิธีการที่ผู้โทรและในหลายกรณี (เช่นที่make_shared/_uniqueสามารถใช้งานได้) ฟังก์ชันที่ถูกเรียกไม่จำเป็นต้องหรือnew deleteคำตอบนี้พลาดจุดที่แท้จริง: (A) C ++ ให้สิ่งต่าง ๆ เช่น RVO, การย้ายความหมายและพารามิเตอร์ขาออก - ซึ่งมักจะหมายถึงการจัดการการสร้างวัตถุและการขยายอายุการใช้งานโดยการส่งคืนหน่วยความจำที่จัดสรรแบบไดนามิก (B) แม้ในสถานการณ์ที่จำเป็นต้องมีการจัดสรรแบบไดนามิก stdlib ยังมี wraps RAII ที่ช่วยให้ผู้ใช้รายละเอียดภายในน่าเกลียด
underscore_d

14

เมื่อคุณใช้ใหม่วัตถุจะถูกจัดสรรให้กับกอง โดยทั่วไปจะใช้เมื่อคุณคาดว่าจะขยายตัว เมื่อคุณประกาศวัตถุเช่น

Class var;

มันถูกวางไว้บนสแต็ก

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


2
+1 "heap] โดยทั่วไปใช้เมื่อคุณคาดว่าจะมีการขยาย" - เช่นต่อท้ายstd::stringหรือstd::mapใช่เข้าใจอย่างลึกซึ้ง ปฏิกิริยาเริ่มต้นของฉันคือ "แต่โดยทั่วไปแล้วจะแยกอายุการใช้งานของวัตถุออกจากขอบเขตการสร้างโค้ด" แต่การคืนค่าหรือรับค่าผู้โทรที่กำหนดขอบเขตโดยไม่constอ้างอิงหรือตัวชี้จะดีกว่ายกเว้นเมื่อมี "การขยาย" ที่เกี่ยวข้อง เกินไป. มีการใช้เสียงอื่น ๆ เช่นวิธีการที่เป็นโรงงานแม้ว่า ....
Tony Delroy

12

เหตุผลหนึ่งที่น่าสังเกตในการหลีกเลี่ยงการใช้ฮีปมากเกินไปสำหรับประสิทธิภาพ - โดยเฉพาะที่เกี่ยวข้องกับประสิทธิภาพของกลไกการจัดการหน่วยความจำเริ่มต้นที่ C ++ ใช้ ในขณะที่การจัดสรรสามารถทำได้ค่อนข้างรวดเร็วในกรณีที่ไม่สำคัญการทำจำนวนมากnewและdeleteบนวัตถุที่มีขนาดไม่สม่ำเสมอโดยไม่มีคำสั่งที่เข้มงวดทำให้ไม่เพียง แต่การกระจายตัวของหน่วยความจำเท่านั้น

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

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


คุณสามารถจัดสรรหน่วยความจำจำนวนมากพอสมควรจากนั้นใช้ตำแหน่งใหม่ / ลบหากความเร็วเป็นปัญหา
rxantos

พูลหน่วยความจำคือเพื่อหลีกเลี่ยงการแตกแฟรกเมนต์เพื่อเร่งการจัดสรรคืน (การจัดสรรคืนหนึ่งครั้งสำหรับอ็อบเจ็กต์หลายพันรายการ) และเพื่อให้การจัดสรรคืนปลอดภัยมากขึ้น
Lothar

10

ผมคิดว่าโปสเตอร์หมายถึงว่ามากกว่านั้นYou do not have to allocate everything on theheapstack

โดยทั่วไปมีการจัดสรรวัตถุบนสแต็ก (ถ้าขนาดวัตถุอนุญาตแน่นอน) เนื่องจากราคาถูกของการจัดสรรสแต็กแทนที่จะจัดสรรตามฮีปซึ่งเกี่ยวข้องกับงานบางอย่างโดยผู้จัดสรรและเพิ่ม verbosity เพราะคุณต้อง จัดการข้อมูลที่จัดสรรบนฮีป


10

ฉันมักจะไม่เห็นด้วยกับแนวคิดของการใช้ "มากเกินไป" ใหม่ แม้ว่าการใช้โปสเตอร์ดั้งเดิมของใหม่กับคลาสของระบบนั้นค่อนข้างไร้สาระ ( int *i; i = new int[9999];จริงๆ? int i[9999];เป็นที่ชัดเจนมากขึ้น.) ฉันคิดว่าเป็นสิ่งที่ได้รับแพะของผู้แสดงความคิดเห็น

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

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


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

When you're working with your own classes/objects... คุณมักจะไม่มีเหตุผลทำเช่นนั้น! สัดส่วนเล็ก ๆ ของ Qs อยู่ในรายละเอียดของการออกแบบภาชนะบรรจุโดยนักเขียนโค๊ชที่มีทักษะ ในทางตรงกันข้ามเป็นสัดส่วนตกต่ำเป็นเรื่องเกี่ยวกับความสับสนของมือใหม่ที่ไม่ทราบว่า STDLIB ที่มีอยู่ - หรือจะได้รับมอบหมายงานอย่างแข็งขันอันยิ่งใหญ่ในการเขียนโปรแกรม ' 'หลักสูตร' ซึ่งเป็นครูสอนพิเศษที่พวกเขาเรียกร้องเปล่าบูรณาการล้อ - ก่อนที่พวกเขาได้แม้กระทั่ง เรียนรู้ว่าล้อคืออะไรและทำไมมันถึงทำงาน ด้วยการส่งเสริมการจัดสรรที่เป็นนามธรรมมากขึ้น C ++ สามารถช่วยเราให้รอดพ้นจาก 'segfault with list ที่เชื่อมโยง' ของ C กรุณาขอให้มัน
underscore_d

" การใช้โปสเตอร์ต้นฉบับใหม่กับคลาสของระบบนั้นค่อนข้างไร้สาระ ( int *i; i = new int[9999];? จริงหรือไม่int i[9999];ชัดเจนกว่านี้มาก)"ใช่มันชัดเจนกว่า แต่การเล่นเป็นทนายของปีศาจประเภทนั้นไม่จำเป็นต้องเป็นอาร์กิวเมนต์ที่ไม่ดี ด้วยองค์ประกอบ 9999 ฉันสามารถจินตนาการถึงระบบฝังตัวที่แน่นหนาที่มีสแต็กไม่เพียงพอสำหรับองค์ประกอบ 9999: 9999x4 ไบต์คือ ~ 40 kB, x8 ~ 80 kB ดังนั้นระบบดังกล่าวอาจต้องใช้การจัดสรรแบบไดนามิกโดยสมมติว่าระบบเหล่านั้นนำไปใช้งานโดยใช้หน่วยความจำสำรอง ถึงกระนั้นนั่นก็อาจพิสูจน์การจัดสรรแบบไดนามิกได้newเท่านั้นไม่ใช่; vectorจะแก้ไขจริงในกรณีที่ว่า
underscore_d

เห็นด้วยกับ @underscore_d - นั่นไม่ใช่ตัวอย่างที่ยอดเยี่ยม ฉันจะไม่เพิ่ม 40,000 หรือ 80,000 ไบต์ในสแต็คของฉันแบบนั้น จริง ๆ แล้วฉันอาจจัดสรรให้กับกอง ( std::make_unique<int[]>()แน่นอน)
einpoklum

3

เหตุผลสองประการ:

  1. มันไม่จำเป็นในกรณีนี้ คุณกำลังทำโค้ดของคุณซับซ้อนยิ่งขึ้นโดยไม่จำเป็น
  2. มันจัดสรรพื้นที่บนฮีปและหมายความว่าคุณต้องจำในdeleteภายหลังหรือจะทำให้หน่วยความจำรั่ว

2

newgotoเป็นใหม่

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

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

ฉันจะเถียงว่าnewมันเลวร้ายยิ่งกว่าgotoเพราะต้องจับคู่newและdeleteแถลงการณ์

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


และฉันจะเพิ่ม: "โดยทั่วไปคุณไม่ต้องการมัน"
einpoklum

1

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

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

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

รูปแบบการออกแบบแบบดั้งเดิมโดยเฉพาะที่กล่าวถึงในหนังสือGoFใช้newมากเพราะเป็นรหัส OO ทั่วไป


4
นี่คือคำตอบสุดซึ้ง For object oriented code, using a pointer [...] is a must: เรื่องไร้สาระ หากคุณกำลังลดค่า 'OO' โดยอ้างอิงเฉพาะเซ็ตย่อยขนาดเล็ก polymorphism - ก็ไร้สาระเช่นกันการอ้างอิงก็ใช้งานได้เช่นกัน [pointer] means use new to create it beforehand: ไร้สาระโดยเฉพาะ : การอ้างอิงหรือตัวชี้สามารถนำไปยังวัตถุที่จัดสรรโดยอัตโนมัติและใช้ polymorphically; ดูฉัน [typical OO code] use new a lot: บางทีในหนังสือเก่าบางเล่ม แต่ใครจะสน? C ++ eschews new/ พอยน์เตอร์ที่ทันสมัยที่สุดไม่ว่าที่ไหนก็ตามที่เป็นไปได้ - & ไม่มีทางใดที่จะลด OO ลงได้
underscore_d

1

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

ในสภาพแวดล้อมดังกล่าวการโทร API ใหม่หรือ C-like จะเป็นที่ต้องการและจำเป็นต้องใช้

แน่นอนว่านี่เป็นเพียงข้อยกเว้นของกฎ


-6

newจัดสรรวัตถุบนกอง มิฉะนั้นวัตถุจะถูกจัดสรรในสแต็ก เงยหน้าขึ้นมองความแตกต่างระหว่างคนทั้งสอง

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