นักพัฒนาซอฟต์แวร์ของ Java ได้ละทิ้ง RAII หรือไม่?


82

ในฐานะโปรแกรมเมอร์ C # เป็นเวลานานฉันเพิ่งได้เรียนรู้เพิ่มเติมเกี่ยวกับข้อดีของการได้มาซึ่งทรัพยากรคือการเริ่มต้น (RAII) โดยเฉพาะฉันได้ค้นพบว่าสำนวน C #:

using (var dbConn = new DbConnection(connStr)) {
    // do stuff with dbConn
}

มี C ++ ที่เทียบเท่า:

{
    DbConnection dbConn(connStr);
    // do stuff with dbConn
}

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

class Foo {
    DbConnection dbConn;

    // ...
}

ใน C # ฉันจะต้องมีการใช้ Foo IDisposableเช่น:

class Foo : IDisposable {
    DbConnection dbConn;

    public void Dispose()
    {       
        dbConn.Dispose();
    }
}

และสิ่งที่แย่กว่านั้นคือผู้ใช้ทุกคนFooจะต้องจำไว้เพื่อใส่ไว้Fooในusingบล็อกเช่น:

   using (var foo = new Foo()) {
       // do stuff with "foo"
   }

ตอนนี้ดู C # และราก Java ของมันฉันสงสัย ... นักพัฒนา Java ไม่ได้ชื่นชมสิ่งที่พวกเขายอมแพ้เมื่อพวกเขาละทิ้งกองซ้อนเพื่อสนับสนุนกองดังนั้นจึงละทิ้ง RAII?

(ในทำนองเดียวกันStroustrupชื่นชมความสำคัญของ RAII อย่างเต็มที่หรือไม่)


5
ฉันไม่แน่ใจว่าสิ่งที่คุณกำลังพูดถึงไม่ได้ล้อมรอบทรัพยากรใน C ++ วัตถุ DBConnection อาจจัดการการปิดทรัพยากรทั้งหมดใน destructor
maple_shaft

16
@maple_shaft ตรงประเด็นของฉัน! นั่นคือข้อดีของ C ++ ที่ฉันพูดถึงในคำถามนี้ ใน C # คุณต้องใส่ทรัพยากรใน "using" ... ใน C ++ คุณไม่ต้อง
JoelFan

12
ความเข้าใจของฉันคือ RAII ซึ่งเป็นกลยุทธ์เข้าใจได้ก็ต่อเมื่อคอมไพเลอร์ C ++ นั้นดีพอที่จะใช้การสร้างเทมเพลตขั้นสูงซึ่งเป็นจริงหลังจาก Java C ++ ที่ใช้งานได้จริงเมื่อสร้าง Java เป็นรูปแบบดั้งเดิม "C กับคลาส" ซึ่งอาจเป็นแม่แบบพื้นฐานหากคุณโชคดี
Sean McMillan

6
"ความเข้าใจของฉันคือ RAII ซึ่งเป็นกลยุทธ์เข้าใจได้ก็ต่อเมื่อคอมไพเลอร์ C ++ นั้นดีพอที่จะใช้การสร้างเทมเพลตขั้นสูงซึ่งเป็นจริงหลังจาก Java" - นั่นไม่ถูกต้องจริงๆ ตัวสร้างและการทำลายล้างเป็นคุณสมบัติหลักของ C ++ ตั้งแต่วันแรกก่อนที่จะมีการใช้เทมเพลตและ Java อย่างแพร่หลาย
Jim In Texas

8
@JimInTexas: ฉันคิดว่าฌอนมีเมล็ดพันธุ์แห่งความจริงขั้นพื้นฐานอยู่ตรงนั้น (แม้ว่าไม่ใช่เทมเพลต แต่ข้อยกเว้นคือปม) Constructors / Destructors อยู่ที่นั่นตั้งแต่แรก แต่มีความสำคัญและแนวคิดของ RAII ไม่ได้เริ่มต้น (ตระหนักถึงสิ่งที่ฉันกำลังมองหา) คำแรก มันใช้เวลาไม่กี่ปีและเวลาในการคอมไพเลอร์ให้ดีก่อนที่เราจะตระหนักถึงความสำคัญของ RAII ทั้งหมด
Martin York

คำตอบ:


38

ตอนนี้ดู C # และราก Java ของมันฉันสงสัย ... นักพัฒนา Java ไม่ได้ชื่นชมสิ่งที่พวกเขายอมแพ้เมื่อพวกเขาละทิ้งกองซ้อนเพื่อสนับสนุนกองดังนั้นจึงละทิ้ง RAII?

(ในทำนองเดียวกัน Stroustrup ชื่นชมความสำคัญของ RAII อย่างเต็มที่หรือไม่)

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

ตลกพอแม้ Stroustrup ไม่ได้ตระหนักถึงความสำคัญของ destructic destructor ในขณะที่เขาออกแบบพวกเขา ฉันไม่สามารถหาคำพูดได้ แต่ถ้าคุณเป็นจริงคุณสามารถหาได้จากการสัมภาษณ์ของเขาที่นี่: http://www.stroustrup.com/interviews.html


11
@maple_shaft: ในระยะสั้นมันเป็นไปไม่ได้ ยกเว้นถ้าคุณคิดค้นวิธีในการเก็บขยะแบบกำหนดค่า (ซึ่งดูเหมือนจะเป็นไปไม่ได้โดยทั่วไปและทำให้การเพิ่มประสิทธิภาพ GC ทั้งหมดในทศวรรษที่ผ่านมาไม่ถูกต้องในทุกกรณี) คุณจะต้องแนะนำวัตถุที่ได้รับการจัดสรรสแต็ก เวิร์ม: วัตถุเหล่านี้ต้องการซีแมนทิกส์ "ปัญหาการแบ่ง" ด้วยการพิมพ์ย่อย (และไม่มีพหุนาม), พอยน์ห้อยนอกจากถ้าคุณวางข้อ จำกัด ที่สำคัญหรือเปลี่ยนแปลงระบบประเภทที่เข้ากันไม่ได้ขนาดใหญ่ และนั่นเป็นเพียงส่วนหัวของฉัน

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

26
@delan ฉันจะไม่เรียกmanualการจัดการหน่วยความจำสมาร์ทพอยน์เตอร์ C ++ พวกเขาเป็นเหมือนนักสะสมขยะที่สามารถควบคุมได้ หากใช้อย่างถูกต้องตัวชี้สมาร์ทเป็นหัวเข่าผึ้ง
Martin York

10
@ LokiAstari: ฉันว่าพวกเขาอัตโนมัติน้อยกว่า GC เต็ม ๆ (คุณต้องคำนึงถึงความฉลาดที่คุณต้องการ) และการนำมันไปใช้ในห้องสมุดจำเป็นต้องมีตัวชี้แบบดิบ (และด้วยเหตุนี้การจัดการหน่วยความจำเอง) . นอกจากนี้ฉันไม่ทราบถึงตัวชี้ที่ฉลาดเกินกว่าจะจัดการอ้างอิงโดยอัตโนมัติซึ่งเป็นข้อกำหนดที่เข้มงวดสำหรับการเก็บขยะในหนังสือของฉัน ตัวชี้อัจฉริยะนั้นยอดเยี่ยมและมีประโยชน์อย่างไม่น่าเชื่อ แต่คุณต้องเผชิญกับพวกเขาไม่สามารถให้การรับประกันบางอย่าง (ไม่ว่าคุณจะคิดว่าพวกเขามีประโยชน์หรือไม่) ในภาษา GC'd อย่างเต็มที่และโดยเฉพาะ

11
@delan: ฉันไม่เห็นด้วยที่นั่น ฉันคิดว่าพวกเขาอัตโนมัติมากกว่า GC เนื่องจากพวกเขากำหนดไว้ ตกลง. เพื่อให้มีประสิทธิภาพคุณต้องให้แน่ใจว่าคุณใช้ที่ถูกต้อง (ฉันจะให้คุณ) std :: weak_ptr จัดการรอบดีอย่างสมบูรณ์ วงจรมักจะถูกตัดให้สั้นลง แต่ในความเป็นจริงมันแทบจะไม่เกิดปัญหาเลยเพราะวัตถุฐานมักจะเป็นสแต็กตามและเมื่อสิ่งนี้เกิดขึ้นจะทำให้ส่วนที่เหลือเป็นระเบียบเรียบร้อยขึ้น สำหรับกรณีที่หายากก็อาจเป็นปัญหา std :: weak_ptr
Martin York

60

ใช่นักออกแบบของ C # (และฉันแน่ใจว่า Java) ได้ตัดสินใจเป็นพิเศษเกี่ยวกับการสรุปที่แน่นอน ฉันถาม Anders Hejlsberg เกี่ยวกับเรื่องนี้หลายครั้งในช่วงปี 1999-2002

อย่างแรกความคิดเกี่ยวกับความหมายที่แตกต่างกันของวัตถุนั้นขึ้นอยู่กับว่า stack - based หรือ heap-based นั้นตรงกันข้ามกับเป้าหมายการออกแบบที่เป็นหนึ่งเดียวของทั้งสองภาษาซึ่งจะช่วยบรรเทาโปรแกรมเมอร์ของปัญหาดังกล่าว

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

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

ใน C # คุณมีusingคำหลักซึ่งมาช้าในการออกแบบสิ่งที่กลายเป็น C # 1.0 IDisposableสิ่งทั้งหมดนั้นค่อนข้างน่าสยดสยองและสิ่งหนึ่งที่น่าสงสัยว่ามันจะสง่างามกว่าถ้าได้usingทำงานกับไวยากรณ์ C + destructor ที่~ทำเครื่องหมายคลาสเหล่านั้นซึ่งIDisposableรูปแบบจานหม้อไอน้ำสามารถใช้งานได้โดยอัตโนมัติหรือไม่


2
สิ่งที่เกี่ยวกับ C ++ / CLI (.NET) ได้ทำไปแล้วที่วัตถุบนฮีปที่ได้รับการจัดการนั้นมี "จัดการ" แบบกองซ้อนซึ่งให้ RIAA ด้วย
JoelFan

3
C ++ / CLI มีชุดการตัดสินใจและข้อ จำกัด ที่แตกต่างกันมาก การตัดสินใจบางอย่างนั้นหมายความว่าคุณสามารถเรียกร้องความคิดเพิ่มเติมเกี่ยวกับการจัดสรรหน่วยความจำและผลกระทบด้านประสิทธิภาพจากโปรแกรมเมอร์: "ให้เชือกพอที่จะแขวนตัวเอง" ทั้งหมด และฉันคิดว่าคอมไพเลอร์ C ++ / CLI นั้นซับซ้อนกว่าของ C # มาก (โดยเฉพาะในรุ่นแรก ๆ )
Larry OBrien

5
+1 นี่เป็นคำตอบเดียวที่ถูกต้อง - เพราะ Java ตั้งใจไม่มีวัตถุแบบอิงกองซ้อน (ไม่ใช่แบบดั้งเดิม)
BlueRaja - Danny Pflughoeft

8
@ Peter Taylor - ใช่ แต่ฉันรู้สึกว่า destructor ที่ไม่ได้กำหนดค่าของ C # มีค่าน้อยมากเนื่องจากคุณไม่สามารถไว้ใจได้ในการจัดการทรัพยากรที่มีข้อ จำกัด ใด ๆ ดังนั้นในความคิดของฉันมันอาจจะดีกว่าที่จะใช้~ไวยากรณ์เป็นน้ำตาล syntactic สำหรับIDisposable.Dispose()
Larry OBrien

3
@ แลร์รี่: ฉันเห็นด้วย c ++ / CLI ไม่ใช้~เป็นน้ำตาลประโยคสำหรับIDisposable.Dispose()และมันมากสะดวกสบายกว่าไวยากรณ์ C #
dan04

41

โปรดทราบว่า Java ได้รับการพัฒนาในปี 1991-1995 เมื่อ C ++ เป็นภาษาที่แตกต่างกันมาก ข้อยกเว้น (ซึ่งจำเป็นต้องมี RAII ) และแม่แบบ (ซึ่งทำให้ง่ายต่อการใช้ตัวชี้สมาร์ท) เป็นคุณสมบัติ "ที่แปลกใหม่" โปรแกรมเมอร์ C ++ ส่วนใหญ่มาจาก C และถูกใช้เพื่อจัดการหน่วยความจำด้วยตนเอง

ดังนั้นฉันสงสัยว่าผู้พัฒนาของ Java จงใจตัดสินใจละทิ้ง RAII อย่างไรก็ตามมันเป็นการตัดสินใจโดยเจตนาสำหรับ Java ที่จะชอบซีแมนทิกส์อ้างอิงแทนที่จะเป็นซีแมนทิกส์ตามตัวอักษร การทำลายล้างที่กำหนดได้ยากที่จะนำไปใช้ในภาษาอ้างอิงความหมาย

เหตุใดจึงต้องใช้ซีแมนทิกส์อ้างอิงแทนซีแมนทิกส์คุณค่า

เพราะมันทำให้ภาษามากง่าย

  • ไม่จำเป็นต้องมีความแตกต่างระหว่างประโยคไม่เป็นFooและFoo*หรือระหว่างและfoo.barfoo->bar
  • ไม่จำเป็นสำหรับการมอบหมายที่มากเกินไปเมื่อการมอบหมายทั้งหมดทำสำเนาตัวชี้
  • ไม่จำเป็นต้องใช้ตัวสร้างสำเนา ( บางครั้งจำเป็นต้องมีฟังก์ชั่นการคัดลอกอย่างชัดเจนเช่นclone()วัตถุจำนวนมากไม่จำเป็นต้องคัดลอกตัวอย่างเช่นไม่สามารถเปลี่ยนได้)
  • ไม่จำเป็นต้องประกาศตัวprivateสร้างสำเนาและoperator=ทำให้คลาสไม่สามารถคัดลอกได้ หากคุณไม่ต้องการคัดลอกวัตถุของคลาสคุณเพียงแค่ไม่เขียนฟังก์ชันเพื่อคัดลอก
  • ไม่จำเป็นต้องใช้swapฟังก์ชั่น (ยกเว้นว่าคุณกำลังเขียนชุดคำสั่งการเรียงลำดับ)
  • ไม่จำเป็นต้องใช้สำหรับการอ้างอิงค่าของ C ++ 0x-style
  • ไม่จำเป็นต้องมี (N) RVO
  • ไม่มีปัญหาการตัด
  • คอมไพเลอร์สามารถกำหนดโครงร่างของวัตถุได้ง่ายขึ้นเนื่องจากการอ้างอิงมีขนาดคงที่

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

Java เลือกใช้ตัวรวบรวมขยะที่ไม่ได้กำหนดค่า

GC ไม่สามารถกำหนดได้หรือไม่

ใช่มันสามารถ ตัวอย่างเช่นการใช้งาน C ของPythonใช้การนับการอ้างอิง และต่อมาได้เพิ่มการติดตาม GC เพื่อจัดการกับขยะที่เป็นวงจรที่ refcounts ล้มเหลว

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

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

แต่สิ่งที่เกี่ยวกับ C ++ / CLI "วัตถุสแต็ก"?

พวกเขากำลังเพียงน้ำตาลประโยคสำหรับDispose ( ลิงค์เดิม ) มากเช่น C using# อย่างไรก็ตามมันไม่ได้แก้ปัญหาทั่วไปของการทำลายล้างที่กำหนดขึ้นเนื่องจากคุณสามารถสร้างแบบไม่ระบุชื่อgcnew FileStream("filename.ext")และ C ++ / CLI จะไม่กำจัดมันโดยอัตโนมัติ


3
นอกจากนี้การเชื่อมโยงที่ดี(โดยเฉพาะคนแรกซึ่งเป็นอย่างสูงที่เกี่ยวข้องกับการสนทนานี้)
BlueRaja - Danny Pflughoeft

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

1
"ไม่จำเป็นต้องใช้ตัวสร้างการคัดลอก" ฟังดูดี แต่ล้มเหลวในทางปฏิบัติในทางที่ไม่ดี java.util.Date และปฏิทินอาจเป็นตัวอย่างที่มีชื่อเสียงมากที่สุด ไม่มีอะไร lovelier new Date(oldDate.getTime())กว่า
วินไคลน์

2
iOW RAII ไม่ใช่ "ถูกทอดทิ้ง" มันไม่มีอยู่จริงที่จะถูกทอดทิ้ง :) ในการคัดลอกคอนสตรัคเตอร์ฉันไม่เคยชอบพวกเขาง่ายเกินไปที่จะเข้าใจผิดพวกเขาเป็นแหล่งที่มาของอาการปวดหัวอย่างต่อเนื่อง (อื่น) ลืมที่จะทำสำเนาลึกทำให้ทรัพยากรที่ใช้ร่วมกันระหว่างสำเนาที่ไม่ควร
jwenting

20

Java7 แนะนำสิ่งที่คล้ายกับ C # using: คำชี้แจงลองกับทรัพยากร

tryคำสั่งที่ประกาศหนึ่งหรือมากกว่าทรัพยากร ทรัพยากรเป็นวัตถุที่จะต้องปิดโปรแกรมหลังจากเสร็จสิ้นกับมัน tryคำสั่งเมื่อใช้ทรัพยากรเพื่อให้แน่ใจว่าแต่ละทรัพยากรปิดท้ายของคำสั่ง วัตถุใด ๆ ที่ใช้java.lang.AutoCloseableซึ่งรวมถึงวัตถุทั้งหมดที่ใช้งานjava.io.Closeableสามารถใช้เป็นทรัพยากร ...

ดังนั้นฉันคิดว่าพวกเขาอาจไม่เลือกที่จะไม่ใช้งาน RAII หรือเปลี่ยนใจในขณะเดียวกัน


น่าสนใจ แต่ดูเหมือนว่าจะใช้ได้กับวัตถุที่ใช้งานjava.lang.AutoCloseableเท่านั้น อาจไม่ใช่เรื่องใหญ่ แต่ฉันไม่ชอบความรู้สึกที่ค่อนข้าง จำกัด บางทีฉันมีวัตถุอื่น ๆ ที่ควรจะเกี่ยวข้องโดยอัตโนมัติ แต่มันแปลกมากที่จะทำให้มันใช้AutoCloseable...
FrustratedWithFormsDesigner

9
@ Patrick: เอ่องั้นเหรอ? usingไม่เหมือนกับ RAII - ในกรณีหนึ่งผู้โทรต้องกังวลเกี่ยวกับการทิ้งทรัพยากรในอีกกรณีหนึ่งผู้รับโทรศัพท์จะจัดการ
BlueRaja - Danny Pflughoeft

1
+1 ฉันไม่รู้เกี่ยวกับการลองกับทรัพยากร มันควรจะมีประโยชน์ในการทิ้งหม้อไอน้ำมากขึ้น
jprete

3
-1 สำหรับusing/ ลองกับทรัพยากรที่ไม่เหมือนกับ RAII
Sean McMillan

4
@Sean: เห็นด้วย usingและตระกูลก็ไม่มีที่ไหนใกล้กับ RAII
DeadMG

18

Java จงใจไม่มีวัตถุตาม stack (aka ค่าวัตถุ -) สิ่งเหล่านี้จำเป็นต้องมีวัตถุที่ถูกทำลายโดยอัตโนมัติในตอนท้ายของวิธีการเช่นนั้น

เนื่องจากสิ่งนี้และความจริงที่ว่า Java เก็บขยะการสรุปที่กำหนดขึ้นจึงเป็นไปไม่ได้มากขึ้นหรือน้อยลง(เช่นถ้าวัตถุ "ท้องถิ่น" ของฉันถูกอ้างอิงถึงที่อื่นแล้วเมื่อวิธีการสิ้นสุดเราไม่ต้องการให้มันถูกทำลาย ) .

อย่างไรก็ตามนี่เป็นสิ่งที่ดีกับพวกเราส่วนใหญ่เพราะแทบจะไม่จำเป็นต้องมีการสรุปที่กำหนดแน่นอนยกเว้นเมื่อมีการโต้ตอบกับทรัพยากรดั้งเดิม (C ++)!


เหตุใด Java จึงไม่มีวัตถุแบบกองซ้อน

(นอกเหนือจากดั้งเดิม .. )

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

return myObject;
  • ถ้าmyObjectเป็นวัตถุที่ใช้สแต็คในท้องถิ่นตัวคัดลอกคอนสตรัคเตอร์จะถูกเรียก (ถ้าผลลัพธ์ถูกกำหนดให้กับบางสิ่ง)
  • ถ้าmyObjectเป็นออบเจ็กต์ที่ใช้โลคัลและเราส่งคืนการอ้างอิงผลลัพธ์จะไม่ได้กำหนด
  • ถ้าmyObjectเป็นวัตถุสมาชิก / โกลบอลตัวสร้างสำเนาจะถูกเรียก (ถ้าผลลัพธ์ถูกกำหนดให้กับบางสิ่ง)
  • ถ้าmyObjectเป็นวัตถุสมาชิก / ส่วนกลางและเราส่งคืนการอ้างอิงการอ้างอิงจะถูกส่งคืน
  • ถ้าmyObjectเป็นตัวชี้ไปยังวัตถุที่ใช้สแต็คท้องถิ่นผลที่ได้คือไม่ได้กำหนด
  • ถ้าmyObjectเป็นตัวชี้ไปยังวัตถุสมาชิก / ทั่วโลกตัวชี้นั้นจะถูกส่งกลับ
  • ถ้าmyObjectเป็นตัวชี้ไปยังวัตถุที่ใช้ฮีปตัวชี้นั้นจะถูกส่งกลับ

ตอนนี้รหัสเดียวกันทำอะไรใน Java?

return myObject;
  • การอ้างอิงถึงmyObjectถูกส่งคืน ไม่สำคัญว่าตัวแปรนั้นจะเป็นแบบโลคัลสมาชิกหรือโกลบอล และไม่มีวัตถุหรือกรณีตัวชี้สแต็คที่ต้องกังวลเกี่ยวกับ

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


6
ฉันไม่รู้ว่าคุณหมายถึงอะไรโดย "ไม่มีจุดใน RAII" ... ฉันคิดว่าคุณหมายถึง "ไม่มีความสามารถในการให้ RAII ใน Java" ... RAII เป็นอิสระจากภาษาใด ๆ ... มันไม่ได้ กลายเป็น "ไม่มีจุดหมาย" เพราะภาษาใดภาษาหนึ่งไม่ได้จัดเตรียมไว้ให้เลย
JoelFan

4
นั่นไม่ใช่เหตุผลที่ถูกต้อง วัตถุไม่จำเป็นต้องมีอยู่จริงบนกองซ้อนเพื่อใช้ RAII แบบกองซ้อน หากมีสิ่งใดสิ่งหนึ่งที่เป็น "การอ้างอิงที่ไม่ซ้ำ" ผู้ทำลายล้างสามารถถูกยิงได้เมื่อมันออกนอกขอบเขต ดูตัวอย่างการทำงานกับภาษาการเขียนโปรแกรม D: d-programming-language.org/exception-safe.html
Nemanja Trifunovic

3
@Nemanja: วัตถุไม่จำเป็นต้องอยู่บนสแต็กเพื่อให้มีความหมายตาม stack และฉันไม่เคยพูดว่ามันทำ แต่นั่นไม่ใช่ปัญหา ปัญหาดังที่ฉันได้กล่าวไว้คือซีแมนทิกส์แบบอิงสแต็กเอง
BlueRaja - Danny Pflughoeft

4
@Aaraught: ปีศาจอยู่ใน "เกือบตลอด" และ "เวลาส่วนใหญ่" หากคุณไม่ปิดการเชื่อมต่อ db ของคุณและปล่อยให้มันเชื่อมต่อกับ GC เพื่อเปิดใช้งาน finalizer มันจะทำงานได้ดีกับการทดสอบหน่วยของคุณและทำลายอย่างรุนแรงเมื่อนำไปใช้ในการผลิต การล้างค่าที่กำหนดได้นั้นสำคัญมากโดยไม่คำนึงถึงภาษา
Nemanja Trifunovic

8
@NemanjaTrifunovic: ทำไมคุณถึงทดสอบหน่วยในการเชื่อมต่อฐานข้อมูลสด นั่นไม่ใช่การทดสอบหน่วย ไม่ขอโทษฉันไม่ได้ซื้อ คุณไม่ควรสร้างการเชื่อมต่อฐานข้อมูลทั่วสถานที่อย่างไรก็ตามคุณควรผ่านการเชื่อมต่อผ่านคอนสตรัคเตอร์หรือคุณสมบัติและนั่นหมายความว่าคุณไม่ต้องการซีแมนทิกส์ทำลายล้างอัตโนมัติแบบกองซ้อน วัตถุน้อยมากที่ขึ้นอยู่กับการเชื่อมต่อฐานข้อมูลควรเป็นเจ้าของมัน หากการล้างข้อมูลที่ไม่ได้กำหนดค่านั้นกัดคุณบ่อยครั้งสิ่งนั้นยากนั่นเป็นเพราะการออกแบบแอปพลิเคชันที่ไม่ดีไม่ใช่การออกแบบภาษาที่ไม่ดี
Aaronaught

17

คำอธิบายหลุมของคุณusingไม่สมบูรณ์ พิจารณาปัญหาต่อไปนี้:

interface Bar {
    ...
}
class Foo : Bar, IDisposable {
    ...
}

Bar b = new Foo();

// Where's the Dispose?

ในความคิดของฉันการไม่มีทั้ง RAII และ GC เป็นความคิดที่ไม่ดี เมื่อมันมาถึงการปิดไฟล์ใน Java มันmalloc()และที่free()นั่น


2
ฉันยอมรับว่า RAII เป็นหัวเข่าของผึ้ง แต่usingข้อเป็นขั้นตอนที่ยอดเยี่ยมสำหรับ C # มากกว่า Java มันทำให้เกิดการทำลายล้างที่กำหนดขึ้นและทำให้การจัดการทรัพยากรถูกต้อง (มันค่อนข้างไม่ดีเท่า RAII อย่างที่คุณต้องจำให้ทำ แต่มันเป็นความคิดที่ดีจริงๆ)
Martin York

8
“ เมื่อพูดถึงการปิดไฟล์ใน Java มันเป็น malloc () และฟรี () ตรงนั้น” - แน่นอน
Konrad Rudolph

9
@ KonradRudolph: มันเลวร้ายยิ่งกว่า malloc และฟรี อย่างน้อยใน C คุณไม่มีข้อยกเว้น
Nemanja Trifunovic

1
@Nemanja: Let 's เป็นธรรมที่คุณสามารถในfree() finally
DeadMG

4
@ Loki: ปัญหาระดับฐานมีความสำคัญมากกว่าปัญหา ตัวอย่างเช่นต้นฉบับIEnumerableไม่ได้สืบทอดมาIDisposableและมีตัววนซ้ำพิเศษจำนวนหนึ่งซึ่งไม่สามารถนำมาใช้เป็นผลได้
DeadMG

14

ฉันแก่แล้ว ฉันเคยไปที่นั่นและเห็นมันและกระแทกหัวของฉันเกี่ยวกับเรื่องนี้หลายครั้ง

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

ตอนนี้พวกเขาเพิ่ม Finalisers ให้กับสเป็คภาษา

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

จากนั้นเรามี C # และฉันจำฟอรัมสนทนาใน MSDN ที่เราบอกว่าภาษา C # ใหม่นี้วิเศษเพียงใด มีคนถามว่าทำไมไม่มีการสรุปที่แน่นอนและเด็กชาย MS บอกเราว่าเราไม่ต้องการสิ่งนั้นแล้วบอกเราว่าเราต้องเปลี่ยนวิธีการออกแบบแอพของเราแล้วบอกเราว่า GC น่าทึ่งแค่ไหนและแอปเก่าทั้งหมดของเราเป็นอย่างไร ขยะและไม่เคยทำงานเพราะการอ้างอิงแบบวงกลมทั้งหมด จากนั้นพวกเขายุบตัวลงเพื่อกดดันและบอกกับเราว่าพวกเขาได้เพิ่มรูปแบบ IDispose นี้ให้กับสเป็คที่เราสามารถใช้ได้ ฉันคิดว่ามันค่อนข้างกลับไปสู่การจัดการหน่วยความจำด้วยตนเองสำหรับเราในแอป C # ณ จุดนั้น

แน่นอนเด็กชาย MS ค้นพบในภายหลังว่าสิ่งที่พวกเขาบอกกับเราคือ ... พวกเขาทำให้ IDispose มากกว่าอินเทอร์เฟซมาตรฐานและเพิ่มคำสั่งการใช้ในภายหลัง w00t! พวกเขาตระหนักดีว่าการกำหนดขั้นสุดท้ายแบบกำหนดแน่นอนนั้นเป็นสิ่งที่ขาดหายไปจากภาษา แน่นอนคุณยังต้องจำไว้ว่าให้ใส่มันทุกที่ดังนั้นมันยังคงเป็นคู่มือเล็กน้อย แต่จะดีกว่า

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

แต่ฉันก็คิดว่าการขาดวิธีการสุดท้ายใน Java ดั้งเดิมนั้นมีมากกว่านั้น - วัตถุที่คุณสร้างล้วนเกี่ยวกับความทรงจำและถ้าคุณต้องการลบอย่างอื่น (เช่นตัวจัดการ DB หรือซ็อกเก็ตหรืออะไรก็ตาม) ) แล้วคุณคาดว่าจะทำด้วยตนเอง

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

ดังนั้น IMHO วิธีที่. NET คัดลอกข้อบกพร่องที่ใหญ่ที่สุดของ Java ก็คือจุดอ่อนที่ยิ่งใหญ่ที่สุด .NET ควรเป็น C ++ ที่ดีกว่าไม่ใช่ Java ที่ดีกว่า


IMHO สิ่งต่างๆเช่นบล็อก 'ใช้' เป็นวิธีที่เหมาะสมสำหรับการล้างข้อมูลที่กำหนดขึ้น แต่จำเป็นต้องมีอีกสองสามสิ่งเช่นกัน: (1) วิธีการทำให้มั่นใจว่าวัตถุจะถูกกำจัดหาก destructors ของพวกเขามีข้อยกเว้น; (2) วิธีการสร้างวิธีการอัตโนมัติเพื่อเรียกใช้Disposeในทุกฟิลด์ที่มีusingคำสั่งและระบุว่าIDisposable.Disposeควรจะเรียกมันโดยอัตโนมัติหรือไม่ (3) คำสั่งที่คล้ายกับusingแต่จะเรียกเฉพาะDisposeในกรณีที่มีข้อยกเว้น; (4) การเปลี่ยนแปลงIDisposableที่จะใช้Exceptionพารามิเตอร์และ ...
supercat

... ซึ่งจะถูกใช้โดยอัตโนมัติusingหากเหมาะสม พารามิเตอร์จะเป็นnullถ้าusingบล็อกออกจากปกติหรืออื่น ๆ จะระบุว่ามีข้อยกเว้นใดที่ค้างอยู่หากออกจากข้อยกเว้น ถ้าสิ่งนั้นมีอยู่มันจะง่ายกว่ามากในการจัดการทรัพยากรอย่างมีประสิทธิภาพและหลีกเลี่ยงการรั่วไหล
supercat

11

Bruce Eckel ผู้แต่ง "Thinking in Java" และ "Thinking in C ++" และสมาชิกของคณะกรรมการมาตรฐาน C ++ มีความเห็นว่าในหลาย ๆ พื้นที่ (ไม่ใช่แค่ RAII) Gosling และทีม Java ไม่ได้ทำสิ่งเหล่านี้ การบ้าน.

... เพื่อให้เข้าใจว่าภาษานั้นมีทั้งที่ไม่พึงประสงค์และซับซ้อนได้อย่างไรและได้รับการออกแบบมาอย่างดีในเวลาเดียวกันคุณต้องคำนึงถึงการตัดสินใจออกแบบเบื้องต้นที่ทุกอย่างในภาษา C ++ จะแขวน: เข้ากันได้กับ C. Stroustrup ตัดสินใจ - และถูกต้อง มันจะปรากฏขึ้น - วิธีที่จะทำให้มวลของโปรแกรมเมอร์ C ย้ายไปที่วัตถุคือทำให้การย้ายโปร่งใส: เพื่อให้พวกเขารวบรวมรหัส C ของพวกเขาไม่เปลี่ยนแปลงภายใต้ C ++ นี่เป็นข้อ จำกัด อย่างมากและเป็นจุดแข็งที่สุดของ C ++ เสมอ ... และความหายนะ มันคือสิ่งที่ทำให้ C ++ ประสบความสำเร็จอย่างที่เคยเป็นมาและมีความซับซ้อนอย่างที่เป็นอยู่

นอกจากนี้ยังหลอกนักออกแบบ Java ที่ไม่เข้าใจ C ++ ดีพอ ตัวอย่างเช่นพวกเขาคิดว่าการใช้งานเกินกำลังนั้นยากเกินกว่าที่โปรแกรมเมอร์จะใช้อย่างถูกต้อง ซึ่งโดยทั่วไปแล้วเป็นจริงใน C ++ เนื่องจาก C ++ มีทั้งการจัดสรรสแต็กและการจัดสรรฮีปและคุณต้องโอเวอร์โหลดโอเปอเรเตอร์ของคุณเพื่อจัดการกับทุกสถานการณ์และไม่ทำให้หน่วยความจำรั่ว ยากแน่นอน อย่างไรก็ตาม Java มีกลไกการจัดสรรหน่วยเก็บข้อมูลเดียวและตัวรวบรวมขยะซึ่งทำให้ผู้ปฏิบัติงานโอเวอร์โหลดเล็กน้อย - ดังที่แสดงใน C # (แต่ได้ถูกแสดงใน Python ซึ่งมี Java ไว้ล่วงหน้าแล้ว) แต่หลายปีที่ผ่านมาสายงานบางส่วนจากทีมจาวาคือ "การใช้งานมากเกินไปซับซ้อนเกินไป" การตัดสินใจนี้และอื่น ๆ อีกมากมายที่ใครบางคนไม่ได้ทำอย่างชัดเจน '

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

เมื่อฉันเขียนเกี่ยวกับวิธีการออกแบบ generics ที่ไม่ดีฉันได้รับคำตอบเดียวกันพร้อมกับ "เราต้องย้อนกลับเข้ากันได้กับการตัดสินใจก่อนหน้านี้ (ไม่ดี) ใน Java" เมื่อเร็ว ๆ นี้ผู้คนจำนวนมากขึ้นได้รับประสบการณ์มากพอกับ Generics เพื่อดูว่าพวกเขาใช้งานยากจริง ๆ - จริง ๆ แล้วเทมเพลต C ++ นั้นมีประสิทธิภาพและสอดคล้องกันมากขึ้น (และใช้งานง่ายกว่ามาก ผู้คนยังคงให้ความสำคัญอย่างจริงจัง - สิ่งที่จะเป็นประโยชน์ แต่จะไม่ทำให้คนส่วนใหญ่ตกอยู่ในการออกแบบที่พิการด้วยข้อ จำกัด ที่บังคับด้วยตนเอง

รายการไปยังจุดที่น่าเบื่อเพียง ...


5
ฟังดูเหมือนคำตอบของ Java และ C ++ แทนที่จะเน้นที่ RAII ฉันคิดว่า C ++ และ Java เป็นภาษาที่แตกต่างกันแต่ละคนมีจุดแข็งและจุดอ่อน นักออกแบบ C ++ ไม่ได้ทำการบ้านในหลาย ๆ ด้าน (ไม่ใช้หลักการ KISS, กลไกการนำเข้าอย่างง่ายสำหรับการขาดเรียน ฯลฯ ) แต่จุดสำคัญของคำถามคือ RAII: สิ่งนี้หายไปใน Java และคุณต้องตั้งโปรแกรมด้วยตนเอง
Giorgio

4
@Giorgio: ประเด็นของบทความคือ Java ดูเหมือนจะพลาดเรือในหลายประเด็นบางประเด็นที่เกี่ยวข้องโดยตรงกับ RAII เกี่ยวกับ C ++ และผลกระทบต่อ Java, Eckels กล่าวว่า: "คุณต้องระลึกไว้เสมอว่าการตัดสินใจออกแบบหลักที่ทุกอย่างใน C ++ จะแขวน: ความเข้ากันได้กับ C นี่เป็นข้อ จำกัด อย่างมากและเป็นจุดแข็งที่สุดของ C ++ ... และ หายนะนอกจากนี้ยังหลอกนักออกแบบ Java ที่ไม่เข้าใจภาษาซีพลัสพลัสดีพอ " การออกแบบของ C ++ มีอิทธิพลต่อ Java โดยตรงในขณะที่ C # มีโอกาสเรียนรู้จากทั้งคู่ (ไม่ว่าจะเป็นคำถามอื่นหรือไม่)
Gnawme

2
@Giorgio การศึกษาภาษาที่มีอยู่ในกระบวนทัศน์และครอบครัวภาษานั้นเป็นส่วนหนึ่งของการบ้านที่จำเป็นสำหรับการพัฒนาภาษาใหม่ นี่คือตัวอย่างหนึ่งที่พวกเขาเพียงแค่ whiffed กับ Java พวกเขามี C ++ และ Smalltalk เพื่อดู C ++ ไม่มี Java ให้ดูเมื่อพัฒนาแล้ว
Jeremy

1
@Gnawme: "Java ดูเหมือนจะพลาดเรือในหลายประเด็นบางประเด็นที่เกี่ยวข้องโดยตรงกับ RAII": คุณสามารถพูดถึงปัญหาเหล่านี้ได้หรือไม่? บทความที่คุณโพสต์ไม่ได้พูดถึง RAII
Giorgio

2
@Giorgio Sure มีนวัตกรรมมาตั้งแต่การพัฒนา C ++ ซึ่งเป็นสาเหตุของฟีเจอร์มากมายที่คุณขาดไป มีฟีเจอร์เหล่านั้นที่พวกเขาควรจะได้พบดูภาษาที่สร้างขึ้นก่อนการพัฒนา C ++ หรือไม่? นั่นคือการบ้านที่เรากำลังพูดถึงกับ Java - ไม่มีเหตุผลที่พวกเขาจะไม่พิจารณาคุณสมบัติ C ++ ทุกอย่างใน develpoment ของ Java บางคนชอบมรดกหลายอย่างที่พวกเขาตั้งใจออก - คนอื่น ๆ เช่น RAII ที่พวกเขามองข้าม
Jeremy

10

เหตุผลที่ดีที่สุดนั้นง่ายกว่าคำตอบส่วนใหญ่ที่นี่

คุณไม่สามารถส่งวัตถุที่จัดสรรสแต็กไปยังเธรดอื่นได้

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

Java ได้รับการออกแบบจาก (เกือบ) วันที่หนึ่งสำหรับหลายกระทู้

ฉันยังได้รับสำเนาของ "ภาษาการเขียนโปรแกรม C ++" ที่ Stroustrup ยืนยันกับฉันว่าฉันไม่ต้องการเธรด

เหตุผลที่เจ็บปวดที่สองคือการหลีกเลี่ยงการแบ่ง


1
Java ถูกออกแบบมาสำหรับหลายเธรดยังอธิบายถึงสาเหตุที่ GC ไม่ได้ขึ้นอยู่กับการนับการอ้างอิง
dan04

4
@NemanjaTrifunovic: คุณไม่สามารถเปรียบเทียบ C ++ / CLI กับ Java หรือ C # ได้ถูกออกแบบมาเพื่อวัตถุประสงค์ในการประสานงานกับโค้ด C / C ++ ที่ไม่มีการจัดการอย่างชัดเจน มันเป็นเหมือนภาษาที่ไม่มีการจัดการที่ให้การเข้าถึง. NET Framework มากกว่าในทางกลับกัน
Aaronaught

3
@NemanjaTrifunovic: Yes, c ++ / CLI เป็นตัวอย่างหนึ่งของวิธีการที่จะสามารถทำได้ในทางที่เป็นทั้งหมดที่ไม่เหมาะสมกับการใช้งานปกติ มันจะมีประโยชน์สำหรับ C / C ++ interop เท่านั้น ไม่เพียง แต่นักพัฒนาปกติไม่จำเป็นต้องมีการตัดสินใจ "stack หรือ heap" ที่ไม่เกี่ยวข้องทั้งหมด แต่ถ้าคุณพยายาม refactor มันก็เป็นการง่ายที่จะสร้างตัวชี้โมฆะ / อ้างอิงข้อผิดพลาดและ / หรือการรั่วไหลของหน่วยความจำโดยไม่ตั้งใจ ขออภัยฉันต้องสงสัยว่าคุณเคยตั้งโปรแกรมจริง ๆ ใน Java หรือ C # หรือไม่เพราะฉันไม่คิดว่าทุกคนที่ต้องการความหมายที่ใช้ใน C ++ / CLI
Aaronaught

2
@Aaraught: ฉันตั้งโปรแกรมด้วย Java (เล็กน้อย) และ C # (มาก) และโครงการปัจจุบันของฉันก็ค่อนข้าง C # เชื่อฉันฉันรู้ว่าฉันกำลังพูดถึงอะไรและไม่มีส่วนเกี่ยวข้องกับ "stack vs. heap" - มีทุกสิ่งที่ต้องทำเพื่อให้แน่ใจว่าทรัพยากรทั้งหมดของคุณจะได้รับการเผยแพร่ทันทีที่คุณไม่ต้องการ อัตโนมัติ หากพวกเขาไม่ใช่ - คุณจะเดือดร้อน
Nemanja Trifunovic

4
@NemanjaTrifunovic: ยอดเยี่ยมยอดเยี่ยมจริงๆ แต่ทั้ง C # และ C ++ / CLI ต้องการให้คุณระบุอย่างชัดเจนเมื่อคุณต้องการให้สิ่งนี้เกิดขึ้นพวกเขาเพียงแค่ใช้ไวยากรณ์ที่แตกต่างกัน ไม่มีใครโต้แย้งประเด็นสำคัญที่คุณกำลังเร่าร้อนเกี่ยวกับ (ว่า "ทรัพยากรจะถูกปล่อยออกมาทันทีที่คุณไม่ต้องการ") แต่คุณกำลังกระโดดโลจิคัลอย่างมหึมาเป็น "ภาษาที่ได้รับการจัดการทั้งหมดควรมีระบบอัตโนมัติ แต่เท่านั้น -sort-of call-stack-based deterministic กำจัด " มันแค่ไม่ถือน้ำ
Aaronaught

5

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

ดังนั้นใช่ฉันคิดว่า (ฉันไม่ได้อยู่ที่นั่นจริงๆ ... ) มันเป็นการตัดสินใจที่รัดกุมโดยมีเป้าหมายในการทำให้ภาษาง่ายต่อการหยิบ แต่ในความคิดของฉันมันเป็นการตัดสินใจที่ไม่ดี จากนั้นอีกครั้งฉันมักชอบปรัชญา C ++ ที่ให้โอกาสโปรแกรมเมอร์ที่จะม้วนตัวเองดังนั้นฉันจึงค่อนข้างเอนเอียง


7
ปรัชญา "ให้ - โปรแกรมเมอร์ -a- โอกาสที่จะหมุน - ของ - ตัวเอง" ทำงานได้ดีจนคุณต้องรวมห้องสมุดที่เขียนโดยโปรแกรมเมอร์ซึ่งแต่ละคนกลิ้งคลาสสตริงของตัวเองและสมาร์ทพอยน์เตอร์
dan04

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

-1

คุณเรียกค่าคร่าวๆที่เทียบเท่ากับค่านี้ใน C # ด้วยDisposeวิธีการ Java finalizeนอกจากนี้ยังมี หมายเหตุ: ฉันตระหนักว่าการสรุปสุดท้ายของ Java นั้นไม่ได้กำหนดและแตกต่างจากDisposeฉันกำลังชี้ให้เห็นว่าพวกเขาทั้งสองมีวิธีการทำความสะอาดทรัพยากรควบคู่ไปกับ GC

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

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

บางทีมันอาจเป็นเพราะความชอบบางอย่างเช่นพลังงานระดับต่ำ, การควบคุมและความบริสุทธิ์ของ C ++ ที่คนอื่นอย่างฉันชอบภาษาแซนด์บ็อกซ์มากกว่าที่ชัดเจนกว่า


12
ก่อนอื่น Java ของ "finalize" ไม่สามารถกำหนดได้ ... มันไม่เท่ากับ C # 's "dispose" หรือของ C ++ destructors ... นอกจากนี้ C ++ ยังมีตัวรวบรวมขยะหากคุณใช้. NET
JoelFan

2
@DeadMG: ปัญหาคือคุณอาจไม่ใช่คนงี่เง่า แต่คนอื่นที่เพิ่งออกจาก บริษัท (และผู้เขียนรหัสที่คุณรักษาไว้ตอนนี้) อาจเป็นได้
เควิน

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

3
สิ่งที่ DeadMG พูด มีสิ่งเลวร้ายมากมายเกี่ยวกับ C ++ แต่ RAII ไม่ได้เป็นหนึ่งในพวกเขาที่ยืดเยื้อ ในความเป็นจริงการขาด Java และ. NET เพื่อบัญชีการจัดการทรัพยากรอย่างถูกต้อง (เพราะหน่วยความจำเป็นทรัพยากรเพียงอย่างเดียวใช่มั้ย) เป็นหนึ่งในปัญหาที่ใหญ่ที่สุด
Konrad Rudolph

8
สุดท้ายในความคิดของฉันคือการออกแบบภัยพิบัติ เนื่องจากคุณกำลังบังคับให้มีการใช้งานวัตถุที่ถูกต้องจากผู้ออกแบบไปยังผู้ใช้ของวัตถุ (ไม่ใช่ในแง่ของการจัดการหน่วยความจำ แต่เป็นการจัดการทรัพยากร) ใน C ++ มันเป็นความรับผิดชอบของนักออกแบบชั้นเรียนที่จะได้รับการจัดการทรัพยากรที่ถูกต้อง (ทำเพียงครั้งเดียว) ใน Java มันเป็นความรับผิดชอบของผู้ใช้ชั้นเรียนที่จะได้รับการจัดการทรัพยากรที่ถูกต้องและจะต้องทำในแต่ละครั้งที่เราใช้คลาส stackoverflow.com/questions/161177/…
Martin York
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.