2010-06-15 34 views
22

Tôi có một câu hỏi đơn giản hy vọng - làm thế nào một bộ nhớ miễn phí được phân bổ trong khối thử khi ngoại lệ xảy ra? Hãy xem xét mã sau đây:Làm cách nào để giải phóng bộ nhớ trong các khối try-catch?

try 
{ 
    char *heap = new char [50]; 
     //let exception occur here 
    delete[] heap; 
} 
catch (...) 
{ 
    cout << "Error, leaving function now"; 
    //delete[] heap; doesn't work of course, heap is unknown to compiler 
    return 1; 
} 

Làm cách nào tôi có thể giải phóng bộ nhớ sau khi phân bổ vùng heap và ngoại lệ trước khi gọi delete[] heap? Có một quy tắc không để cấp phát bộ nhớ trên heap trong các thử .. bắt khối?

+8

Tại sao sử dụng mới, khi bạn có thể sử dụng con trỏ thông minh và RAII-friendly containers? –

+1

@Pavel - Con trỏ thông minh và RAII vẫn có vấn đề về sử dụng phức tạp, đáng chú ý nhất xung quanh chu kỳ gây rò rỉ bộ nhớ và hành vi lạ khi truyền con trỏ xung quanh (đó là lý do tại sao auto_ptr không được sử dụng nhiều). Nếu bạn có một cái gì đó có sáng tạo rõ ràng và xóa điểm và bạn muốn nó có thể được sử dụng trong bất kỳ cách nào ở giữa thì không có gì sai với con trỏ thô. – tloach

+8

Tôi không đồng ý về mặt phân loại. C++ đủ mạnh để thiết kế các trừu tượng rõ ràng thành các lớp rõ ràng với hành vi ngoại lệ thân thiện được chôn bên trong, thay vì phơi bày nó với người dùng. –

Trả lời

32

Nghiên cứu Thành ngữ RAII (Mua tài nguyên là khởi tạo)! Xem ví dụ số Wikipedia article on RAII.

RAII chỉ là ý tưởng chung. Nó được sử dụng ví dụ: trong các lớp mẫuhoặc std::shared_ptr của thư viện chuẩn C++.


giải thích Rất ngắn gọn về các thành ngữ RAII:

Về cơ bản, nó là ++ phiên bản C của try..finally khối được tìm thấy trong một số ngôn ngữ khác. Thành ngữ RAII được cho là linh hoạt hơn.

Nó hoạt động như thế này:

  • Bạn viết một lớp bao bọc xung quanh tài nguyên của bạn (ví dụ bộ nhớ). Trình hủy có trách nhiệm giải phóng tài nguyên.

  • Bạn tạo, dưới dạng biến cục bộ (tự động), một thể hiện của lớp trình bao bọc trong phạm vi. Sau khi thực hiện chương trình rời khỏi phạm vi đó, hàm hủy của đối tượng sẽ được gọi, do đó giải phóng tài nguyên (ví dụ: bộ nhớ).

Điều quan trọng là rằng nó không quan trọng như thế nào phạm vi được kết thúc. Ngay cả khi một ngoại lệ được ném, phạm vi vẫn còn thoát và destructor của đối tượng bao bọc vẫn được gọi.


dụ Rất thô:

// BEWARE: this is NOT a good implementation at all, but is supposed to 
// give you a general idea of how RAII is supposed to work: 
template <typename T> 
class wrapper_around 
{ 
    public: 
    wrapper_around(T value) 
     : _value(value) 
    { } 
    T operator *() 
    { 
     return _value; 
    } 
    virtual ~wrapper_around() 
    { 
     delete _value; // <-- NOTE: this is incorrect in this particular case; 
         // if T is an array type, delete[] ought to be used 
    } 
    private: 
    T _value; 
}; 
// ... 

{ 
    wrapper_around<char*> heap(new char[50]); 
    // ... do something ... 

    // no matter how the { } scope in which heap is defined is exited, 
    // if heap has a destructor, it will get called when the scope is left. 
    // Therefore, delegate the responsibility of managing your allocated 
    // memory to the `wrapper_around` template class. 
    // there are already existing implementations that are much better 
    // than the above, e.g. `std::unique_ptr` and `std::shared_ptr`! 
} 
+0

+1 để chỉ đến khái niệm (RAII) đầu tiên và triển khai (con trỏ thông minh, bất cứ điều gì) sau đó. – pyon

+0

Cảm ơn bạn vì sự xuất sắc. Im khá mới để C + + và trong thời gian được tôi muốn sử dụng thực hiện của riêng tôi thay vì mù quáng bằng cách sử dụng các giải pháp đã thực hiện mà tôi không có ý tưởng những gì họ làm trong thực tế. Tôi sẽ cố gắng thực hiện nó trước hết rồi kiểm tra xem RAII là gì và cách sử dụng nó đúng cách. Cảm ơn bạn một lần nữa. – Kra

+0

Sửa ví dụ của bạn để nó gọi xóa [] –

6

Hoặc di chuyển new trước try, do đó con trỏ vẫn còn trong phạm vi, hoặc sử dụng một con trỏ thông minh như shared_ptr hoặc unique_ptr (trong một pinch , auto_ptr, nhưng nó có vấn đề) sẽ làm sạch cho bạn khi thoát. Ngoại lệ là một lý do rất lớn tại sao con trỏ thông minh lại quan trọng.

+2

Trong tình huống này auto_ptr <> là hoàn hảo (nếu con trỏ thông minh là giải pháp). Nhưng những gì về các phương pháp khác như một container năng động? –

+0

Có thể đã bỏ phiếu +1, nếu trước tiên bạn đã đề cập đến khái niệm chung: RAII. Tuy nhiên, câu trả lời hay. – pyon

+1

@Martin York: Bạn không thể sử dụng 'auto_ptr' với một mảng, phải không? –

0

Cách dễ nhất là khai báo biến trước khối thử và sau đó chỉ cần khởi tạo trong khối.

-4

Đồng ý với câu trả lời trên RAII và con trỏ thông minh.

Tuy nhiên, nếu bạn nhấn mạnh, bạn có thể làm điều này:

try { dangerous operations } 
catch { cleanup; throw; } 
+0

Làm cách nào để bạn dọn dẹp thứ gì đó không nằm trong phạm vi? Tại sao bạn lại ném ngoại lệ? –

+0

(Và bạn quên dọn dẹp trong trường hợp tất cả các hoạt động nguy hiểm đều thành công.) – stakx

+0

tất nhiên, nó phải nằm trong phạm vi. rethrowing ngoại lệ là để cho phép chúng tôi bắt bất kỳ ngoại lệ, mà không giết chết các thông tin lỗi. Mẫu phổ biến ở trên tương đương với 'cuối cùng' trong SEH và các ngôn ngữ lập trình khác. –

9

OK mister Java lập trình viên:

try 
{ 
    // Exception safe dynamic allocation of a block of memory. 
    std::vector<char> heap(50); 

    // DO STUFF 

    // Note in C++ we use stack based objects and their constructor/destructor 
    // TO give a deterministic cleanup, even in the presence of exceptions. 
    // 
    // Look up RAII (bad name for a fantastic concept). 
} 
catch (...) 
{ 
    cout << "Error, leaving function now"; 
    return 1; // Though why you want to return when you have not fixed the exception is 
       // slightly strange. Did you want to rethrow? 
} 
+0

Không, tôi không muốn trở lại. Tôi có các hàm trả về 0 khi mọi thứ đều ổn (cũng chứa các ngoại lệ được xử lý trong khối thử) và khi một hàm nào đó không đúng (ngoại trừ unhandled exception) trả về 1.Của nó lên đến người gọi để xử lý tình hình sau đó. Cảm ơn điểm RAII. – Kra

+2

@Kra: Bất kỳ lý do nào bạn muốn dịch từ ngoại lệ sang giá trị trả về để xử lý lỗi? –

3

Câu trả lời 'đúng' là RAII và shared_ptr như đã đề cập ở trên, nhưng chỉ để được hoàn thành: trong ví dụ của bạn, bạn có thể thay thế

char *heap = new char [50]; 

với

char *stack = static_cast<char*>(alloca(50)); 

alloca là gần như giống hệt nhau để malloc, ngoại trừ việc nó alocs bộ nhớ trên stack thay vì đống, vì vậy không vấn đề làm thế nào bạn có chức năng thoát (ném hoặc bây giờ), bộ nhớ sẽ được khai hoang, và không xóa hoặc giải phóng là cần thiết.

+4

Nếu bạn đang đi xuống con đường đó, chỉ cần làm 'char heap [50];'. Lý do duy nhất để sử dụng 'alloca' là nếu bạn không biết kích thước tại thời gian biên dịch, và bạn không quá bận tâm về tính di động. –

1

Tôi phải đồng ý với tất cả những người đã nói RAII, tuy nhiên, tôi sẽ sử dụng Boost shared_array thay vì auto_ptr. Gọi con trỏ tự động delete và không phải 'xóa []' sẽ gây ra rò rỉ với một mảng.

5

Câu trả lời chung là sử dụng RAII.

Tuy nhiên, có thể của nó để giải quyết nó bằng cách di chuyển biến ra khỏi try {scope}:

char * heap = NULL; 
try { 
    heap = new char [50]; 
    ... stuff ... 
} catch (...) { 
    if (heap) { 
    delete[] heap; 
    heap = NULL; 
    } 
    ... however you want to handle the exception: rethrow, return, etc ... 
} 

Xin lưu ý rằng tôi không đề xuất này như một thói quen tốt - nhưng nhiều hơn một xuống & bẩn chỉ được sử dụng nếu bạn thực sự biết những rủi ro và vẫn sẵn sàng đưa họ. Cá nhân, tôi sẽ sử dụng RAII.

Hòa bình

+2

RAII: Cung cấp tài nguyên là khởi tạo. RIAA là một thực thể độc ác khác. :) –

+3

Tôi nghĩ có lẽ bạn có nghĩa là RAII. Tôi không nghĩ rằng ngành công nghiệp âm nhạc tham gia sẽ giúp ngoại lệ an toàn :) – Peter

+0

Lolls - cảm ơn guys - rõ ràng là khi tôi nghĩ về điều ác, RIAA suối đến tâm trí! ;) – Mordachai

0

Có - nếu bạn đang xem xét sự đơn giản - con trỏ nằm ngoài khối thử của bạn là giải pháp.

Kính trọng

Các vấn đề liên quan