ทำไม Java / C # ไม่สามารถใช้ RAII ได้


29

คำถาม: ทำไม Java / C # ไม่สามารถใช้ RAII ได้

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

ความเข้าใจของฉัน:

ฉันรู้สึกว่าการนำไปปฏิบัติของ RAII ต้องเป็นไปตามข้อกำหนดสองประการ:
1. อายุการใช้งานของทรัพยากรจะต้องอยู่ในขอบเขต
2. โดยปริยาย การเพิ่มทรัพยากรต้องเกิดขึ้นโดยไม่มีคำสั่งอย่างชัดเจนจากโปรแกรมเมอร์ คล้ายกับตัวรวบรวมขยะที่เพิ่มหน่วยความจำโดยไม่ต้องมีคำสั่งที่ชัดเจน "implicitness" จะต้องเกิดขึ้นเมื่อใช้งานในชั้นเรียนเท่านั้น แน่นอนว่าผู้สร้างไลบรารีคลาสต้องใช้เมธอด destructor หรือ Dispose () อย่างชัดเจน

Java / C # พอใจจุดที่ 1 ใน C # ทรัพยากรการใช้ IDisposable สามารถถูกผูกไว้กับขอบเขต "using":

void test()
{
    using(Resource r = new Resource())
    {
        r.foo();
    }//resource released on scope exit
}

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

ในความเป็นจริงแล้วคอมไพเลอร์จะทำการแปลงบล็อค "ใช้" เป็นโค้ด try-final-dispose () มีลักษณะที่ชัดเจนเหมือนกันของรูปแบบ try-final-dispose () หากไม่มีการปล่อยโดยนัยเบ็ดไปยังขอบเขตคือน้ำตาลประโยค

void test()
{
    //Programmer forgot (or was not aware of the need) to explicitly
    //bind Resource to a scope.
    Resource r = new Resource(); 
    r.foo();
}//resource leaked!!!

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

class Resource - ScopeBound
{
    /* class details */

    void Dispose()
    {
        //free resource
    }
}

void test()
{
    //class Resource was flagged as ScopeBound so the tie to the stack is implicit.
    Resource r = new Resource(); //r is a smart-pointer
    r.foo();
}//resource released on scope exit.

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

มันใช้งานไม่ได้กับคุณลักษณะดังกล่าวในภาษา Java / C # หรือไม่? มันจะถูกนำมาใช้โดยไม่ทำลายรหัสเก่าหรือไม่


3
มันไม่ได้ทำไม่ได้มันเป็นไปไม่ได้ C # มาตรฐานไม่ได้รับประกัน destructors / Disposes จะเคยทำงานโดยไม่คำนึงถึงวิธีการที่พวกเขากำลังเรียก การเพิ่มการทำลายโดยนัยเมื่อสิ้นสุดขอบเขตจะไม่ช่วย
Telastyn

20
@Telastyn Huh? สิ่งที่มาตรฐาน C # บอกตอนนี้ไม่มีความเกี่ยวข้องเนื่องจากเรากำลังพูดถึงการเปลี่ยนแปลงเอกสารนั้น ปัญหาเดียวก็คือว่านี่เป็นเรื่องจริงที่ต้องทำและสิ่งที่น่าสนใจเพียงอย่างเดียวเกี่ยวกับการขาดการรับประกันในปัจจุบันคือเหตุผลสำหรับการขาดการรับประกันนี้ โปรดทราบว่าสำหรับusingการดำเนินการของDispose มีการประกัน (ดีลดราคากระบวนการที่กำลังจะตายโดยไม่มีข้อยกเว้นถูกโยนที่จุดที่การล้างข้อมูลทั้งหมดน่าจะกลายเป็นที่สงสัย)

4
ซ้ำกันหรือไม่นักพัฒนาของ Java ได้ละทิ้ง RAII หรือไม่ แม้ว่าคำตอบที่ยอมรับนั้นไม่ถูกต้องสมบูรณ์ คำตอบสั้น ๆ คือ Java ใช้ซีแมนทิกส์อ้างอิง(ฮีป)มากกว่าซีแมนทิกส์คุณค่า(สแต็ค)ดังนั้นการสรุปแบบกำหนดแน่นอนจึงไม่มีประโยชน์ / เป็นไปได้มาก C # จะมีมูลค่าความหมาย ( struct) แต่พวกเขาจะหลีกเลี่ยงโดยปกติยกเว้นในกรณีที่พิเศษมาก ดูเพิ่มเติม
BlueRaja - Danny Pflughoeft

2
มันคล้ายกันไม่ซ้ำกันแน่นอน
Maniero

3
blogs.msdn.com/b/oldnewthing/archive/2010/08/10/10048150.aspxเป็นหน้าเว็บที่เกี่ยวข้องกับคำถามนี้
Maniero

คำตอบ:


17

ส่วนขยายภาษาดังกล่าวจะซับซ้อนและรุกรานอย่างมีนัยสำคัญเกินกว่าที่คุณคิด คุณไม่สามารถเพิ่มได้

หากอายุการใช้งานของตัวแปรชนิดสิ้นสุดสแต็กสิ้นสุดให้เรียกใช้Disposeบนออบเจ็กต์ที่อ้างถึง

ไปยังส่วนที่เกี่ยวข้องของข้อมูลจำเพาะภาษาและดำเนินการ ฉันจะไม่สนใจปัญหาของค่าชั่วคราว ( new Resource().doSomething()) ซึ่งสามารถแก้ไขได้โดยการใช้ถ้อยคำทั่วไปที่มากกว่านี้เล็กน้อยนี่ไม่ใช่ปัญหาที่ร้ายแรงที่สุด ตัวอย่างเช่นรหัสนี้จะใช้งานไม่ได้ (และสิ่งนี้อาจเป็นไปไม่ได้ที่จะทำโดยทั่วไป):

File openSavegame(string id) {
    string path = ... id ...;
    File f = new File(path);
    // do something, perhaps logging
    return f;
} // f goes out of scope, caller receives a closed file

ตอนนี้คุณต้องการตัวสร้างสำเนาที่ผู้ใช้กำหนดเอง (หรือย้ายตัวสร้าง) และเริ่มเรียกใช้พวกเขาทุกที่ สิ่งนี้ไม่เพียงส่งผลต่อประสิทธิภาพการทำงานเท่านั้น แต่ยังทำให้สิ่งเหล่านี้มีค่าประเภทได้อย่างมีประสิทธิภาพในขณะที่วัตถุอื่น ๆ เกือบทั้งหมดเป็นประเภทการอ้างอิง ในกรณีของ Java นี่คือการเบี่ยงเบนที่รุนแรงจากการทำงานของวัตถุ ใน C # น้อยลง (มีอยู่แล้วstructแต่ไม่มีตัวสร้างสำเนาที่ผู้ใช้กำหนดเองสำหรับ AFAIK) แต่ก็ยังทำให้วัตถุ RAII เหล่านี้พิเศษยิ่งขึ้น นอกจากนี้ประเภทเชิงเส้นที่มีจำนวน จำกัด (เช่นสนิม) อาจช่วยแก้ปัญหาได้ด้วยค่าใช้จ่ายในการห้ามนามแฝงรวมถึงการส่งพารามิเตอร์ (ยกเว้นกรณีที่คุณต้องการเพิ่มความซับซ้อนมากยิ่งขึ้น

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


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

1
@bigown หากคุณปฏิบัติต่อการอ้างอิงทุกFileอย่างด้วยวิธีนี้จะไม่มีการเปลี่ยนแปลงและDisposeไม่เคยถูกเรียก หากคุณโทรมาตลอดDisposeคุณจะไม่สามารถทำอะไรกับวัตถุที่ใช้แล้วทิ้งได้ หรือคุณเสนอรูปแบบสำหรับการกำจัดบางครั้งและบางครั้งไม่ ถ้าเป็นเช่นนั้นโปรดอธิบายรายละเอียดและฉันจะบอกคุณในกรณีที่มันล้มเหลว

ฉันไม่เห็นสิ่งที่คุณพูดตอนนี้ (ฉันไม่ได้บอกว่าคุณผิด) วัตถุมีทรัพยากรไม่ใช่การอ้างอิง
Maniero

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

1
@bigown ในคำอื่น ๆ อย่าเรียกDisposeว่าถ้าการอ้างอิงหนีออกมา? การวิเคราะห์การหลบหนีเป็นปัญหาเก่าและยากลำบากซึ่งจะไม่ทำงานเสมอไปหากไม่มีการเปลี่ยนแปลงภาษาเพิ่มเติม เมื่อมีการอ้างอิงถึงวิธีอื่น (เสมือน) ( something.EatFile(f);) ควรf.Disposeเรียกที่ส่วนท้ายของขอบเขตหรือไม่ ถ้าใช่คุณโทรหาผู้ที่เก็บไว้fเพื่อใช้ในภายหลัง ถ้าไม่คุณรั่วไหลของทรัพยากรหากผู้โทรไม่ได้เก็บfไว้ วิธีง่าย ๆ ในการลบนี้เป็นระบบแบบเส้นตรงซึ่ง (ซึ่งฉันได้กล่าวไปแล้วในภายหลังในคำตอบของฉัน) แทนที่จะแนะนำภาวะแทรกซ้อนอื่น ๆ

26

ปัญหาที่ใหญ่ที่สุดในการใช้งานสิ่งนี้สำหรับ Java หรือ C # จะเป็นการกำหนดวิธีการถ่ายโอนทรัพยากรทำงาน คุณจะต้องมีวิธีในการยืดอายุของทรัพยากรเกินขอบเขต พิจารณา:

class IWrapAResource
{
    private readonly Resource resource;
    public IWrapAResource()
    {
        // Where Resource is scope bound
        Resource builder = new Resource(args, args, args);

        this.resource = builder;
    } // Uh oh, resource is destroyed
} // Crap, there's no scope for IWrapAResource we can bind to!

สิ่งที่แย่กว่านั้นคือสิ่งนี้อาจไม่ชัดเจนสำหรับผู้ดำเนินการของIWrapAResource:

class IWrapSomething<T>
{
    private readonly T resource; // What happens if T is Resource?
    public IWrapSomething(T input)
    {
        this.resource = input;
    }
}

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


สมมติว่าคุณไม่จำเป็นต้องอ้างถึงตัวแปรหลังจากที่ไม่อยู่ในขอบเขต (และไม่จำเป็นต้องมีเช่นนั้นจริงๆ) ฉันขออ้างว่าคุณยังสามารถสร้างวัตถุที่กำจัดตัวเองได้โดยการเขียนตัวสุดท้ายสำหรับมัน . Finalizer ถูกเรียกใช้ก่อนที่วัตถุจะถูกเก็บรวบรวมขยะ ดูmsdn.microsoft.com/en-us/library/0s71x931.aspx
Robert Harvey

8
@Robert: โปรแกรมที่เขียนอย่างถูกต้องไม่สามารถถือว่า Finalizers ทำงานได้ blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx
Billy ONeal

1
ฮึ่ม นั่นอาจเป็นเหตุผลว่าทำไมพวกเขาถึงได้ออกusingแถลงการณ์
Robert Harvey

2
เผง นี่คือแหล่งใหญ่ของบั๊กมือใหม่ใน C ++ และจะเป็นใน Java / C # เช่นกัน Java / C # ไม่ได้กำจัดความสามารถในการรั่วไหลของการอ้างอิงไปยังทรัพยากรที่กำลังจะถูกทำลาย แต่ด้วยการทำให้ทั้งชัดเจนและเป็นทางเลือกพวกเขาเตือนโปรแกรมเมอร์และให้เขาเลือกอย่างมีสติในสิ่งที่ต้องทำ
Aleksandr Dubinsky

1
@svick ไม่IWrapSomethingสามารถกำจัดTได้ ใครก็ตามที่สร้างTความต้องการต้องกังวลเกี่ยวกับสิ่งนั้นไม่ว่าจะใช้usingเป็นIDisposableตัวเองหรือมีวงจรชีวิตของทรัพยากร ad-hoc
Aleksandr Dubinsky

13

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

ดังนั้นใน C ++ คุณสามารถทำสิ่งนี้:

void f()
{
    Foo f1;
    Foo* f2 = new Foo();
    Foo::someStaticField = f2;

    // f1 is destroyed here, the object pointed to by f2 isn't
}

ใน C # คุณไม่สามารถแยกความแตกต่างระหว่างสองกรณีดังนั้นคอมไพเลอร์จะไม่มีความคิดว่าจะจบวัตถุหรือไม่

สิ่งที่คุณสามารถทำได้คือการแนะนำชนิดของตัวแปรเฉพาะที่คุณไม่สามารถใส่ลงในฟิลด์และอื่น ๆ * และมันจะถูกกำจัดโดยอัตโนมัติเมื่อมันออกนอกขอบเขต ซึ่งเป็นสิ่งที่ C ++ / CLI ทำ ใน C ++ / CLI คุณเขียนโค้ดดังนี้:

void f()
{
    Foo f1;
    Foo^ f2 = gcnew Foo();
    Foo::someStaticField = f2;

    // f1 is disposed here, the object pointed to by f2 isn't
}

สิ่งนี้จะคอมไพล์ IL โดยพื้นฐานเหมือนกับ C # ต่อไปนี้:

void f()
{
    using (Foo f1 = new Foo())
    {
        Foo f2 = new Foo();
        Foo.someStaticField = f2;
    }
    // f1 is disposed here, the object pointed to by f2 isn't
}

สรุปได้ว่าถ้าฉันต้องเดาว่าทำไมนักออกแบบของ C # ไม่ได้เพิ่ม RAII นั่นเป็นเพราะพวกเขาคิดว่าการมีตัวแปรท้องถิ่นสองประเภทนั้นไม่คุ้มกับมันเพราะส่วนใหญ่ในภาษาที่มี GC การสรุปขั้นสุดท้ายแบบไม่แน่นอนมีประโยชน์ บ่อยครั้ง.

* ไม่ได้โดยไม่มีเทียบเท่าของ&ผู้ประกอบการซึ่งใน C ++ / CLI %คือ แม้ว่าการทำเช่นนั้นจะ“ ไม่ปลอดภัย” ในแง่ที่ว่าหลังจากวิธีการสิ้นสุดลงฟิลด์จะอ้างอิงวัตถุที่ถูกกำจัด


1
C # สามารถทำ RAII ได้เล็กน้อยถ้าอนุญาตให้ destructors สำหรับstructประเภทที่ D ทำ
Jan Hudec

6

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

public void ReadFile ()
{
  string filename = "myFile.dat";
  local Stream file = File.Open(filename);
  file.Read(blah blah blah);
}

ดูlocalคำหลักที่ฉันเพิ่มหรือไม่ ทั้งหมดมันไม่สามารถเพิ่มบิตน้ำตาลประโยคมากขึ้นเช่นเดียวกับusingบอกคอมไพเลอร์ที่จะเรียกDisposeในfinallyบล็อกในตอนท้ายของขอบเขตของตัวแปรที่ นั้นคือทั้งหมด. มันเทียบเท่ากับ:

public void ReadFile ()
{
  string filename = "myFile.dat";
  using (Stream file = File.Open(filename))
  {
      file.Read(blah blah blah);
  }
}

แต่มีขอบเขตโดยนัยแทนที่จะเป็นขอบเขตที่ชัดเจน มันง่ายกว่าคำแนะนำอื่น ๆ เนื่องจากฉันไม่จำเป็นต้องกำหนดคลาสเป็นขอบเขตขอบเขต เพิ่งทำความสะอาดน้ำตาล syntactic นัยมากขึ้น

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


1
@ mike30 แต่การย้ายไปที่การกำหนดประเภทจะนำคุณไปสู่ปัญหาที่คนอื่น ๆ ระบุไว้ - จะเกิดอะไรขึ้นถ้าคุณส่งตัวชี้ไปยังวิธีอื่นหรือส่งคืนจากฟังก์ชัน วิธีนี้จะกำหนดขอบเขตในขอบเขตไม่ใช่ที่อื่น ประเภทอาจเป็นประเภททิ้ง แต่ไม่สามารถเรียกใช้ทิ้งได้
Avner Shahar-Kashtan

3
@ mike30: Meh ไวยากรณ์ทั้งหมดนี้ไม่ได้ลบวงเล็บปีกกาและโดยการขยายการควบคุมการกำหนดที่พวกเขาให้
Robert Harvey

1
@RobertHarvey แน่นอน มันเสียสละความยืดหยุ่นบางอย่างเพื่อทำความสะอาดโค้ดที่ซ้อนกันน้อยลง หากเราใช้คำแนะนำของ @ delnan และนำusingคำหลักนั้นกลับมาใช้ใหม่เราสามารถรักษาพฤติกรรมที่มีอยู่และใช้สิ่งนี้ได้เช่นกันสำหรับกรณีที่เราไม่ต้องการขอบเขตที่เฉพาะเจาะจง มีusingค่าเริ่มต้นรั้งน้อยกว่าขอบเขตปัจจุบัน
Avner Shahar-Kashtan

1
ฉันไม่มีปัญหากับแบบฝึกหัดกึ่งปฏิบัติในการออกแบบภาษา
Avner Shahar-Kashtan

1
@RobertHarvey ดูเหมือนว่าคุณจะมีอคติต่อสิ่งที่ไม่ได้ดำเนินการใน C # ในปัจจุบัน เราจะไม่มี generics, linq, การใช้บล็อก, ipmlicit types, ฯลฯ ถ้าเราพอใจกับ C # 1.0 ไวยากรณ์นี้ไม่ได้แก้ปัญหาของ implicitness แต่มันเป็นน้ำตาลที่ดีในการผูกกับขอบเขตปัจจุบัน
mike30

1

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

with open('output.txt', 'w') as f:
    f.write('Hi there!')

เช่นเดียวกับสไตล์ RAII ของ C ++ ไฟล์จะถูกปิดเมื่อออกจากบล็อกนั้นไม่ว่าจะเป็นการออก 'ปกติ', a break, ทันทีreturnหรือข้อยกเว้น

โปรดทราบว่าการopen()โทรเป็นฟังก์ชั่นเปิดไฟล์ปกติ เพื่อให้งานนี้วัตถุไฟล์ที่ส่งคืนมีสองวิธี:

def __enter__(self):
  return self
def __exit__(self):
  self.close()

นี่เป็นสำนวนทั่วไปใน Python: วัตถุที่เกี่ยวข้องกับทรัพยากรมักจะรวมถึงวิธีการทั้งสองนี้

โปรดทราบว่าวัตถุไฟล์ยังคงสามารถจัดสรรหลังจากการ__exit__()โทรสิ่งที่สำคัญคือมันถูกปิด


7
withใน Python เกือบจะเหมือนกับusingใน C # และไม่เหมือนกับ RAII เท่าที่คำถามนี้เกี่ยวข้อง

1
Python "with" เป็นการจัดการทรัพยากรที่มีขอบเขต แต่ขาดความชัดเจนของตัวชี้สมาร์ท การประกาศตัวชี้เป็นอัจฉริยะอาจถือได้ว่า "ชัดเจน" แต่ถ้าคอมไพเลอร์บังคับใช้ความฉลาดเป็นส่วนหนึ่งของประเภทวัตถุมันจะเอนไปทาง "นัย"
mike30

AFAICT ประเด็นของ RAII คือการกำหนดขอบเขตอย่างเข้มงวดให้กับทรัพยากร หากคุณสนใจที่จะทำโดยการจัดสรรคืนวัตถุเท่านั้นไม่ใช่ภาษาที่รวบรวมขยะไม่สามารถทำได้ หากคุณสนใจที่จะปล่อยทรัพยากรอย่างต่อเนื่องนี่เป็นวิธีที่จะทำ (อีกวิธีหนึ่งคือdeferภาษา Go)
Javier

1
ที่จริงแล้วฉันคิดว่ามันยุติธรรมที่จะบอกว่า Java และ C # สนับสนุนสิ่งก่อสร้างอย่างชัดเจน มิฉะนั้นทำไมต้องกังวลกับพิธีทั้งหมดที่มีอยู่ในการใช้อินเทอร์เฟซและการสืบทอด
Robert Harvey

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