2008-09-29 12 views
18
class someclass {}; 

class base 
{ 
    int a; 
    int *pint; 
    someclass objsomeclass; 
    someclass* psomeclass; 
public: 
    base() 
    { 
     objsomeclass = someclass(); 
     psomeclass = new someclass(); 
     pint = new int(); 
     throw "constructor failed"; 
     a = 43; 
    } 
} 

int main() 
{ 
    base temp(); 
} 

Trong đoạn mã trên, trình xây dựng sẽ ném. Các đối tượng nào sẽ bị rò rỉ và làm thế nào để tránh rò rỉ bộ nhớ?Đoạn mã dưới đây có gây ra rò rỉ bộ nhớ trong C++

int main() 
{ 
    base *temp = new base(); 
} 

Làm thế nào về đoạn mã trên? Làm thế nào có thể bị rò rỉ bộ nhớ sau khi các nhà xây dựng ném?

+1

Tôi biết, tôi có một bản chất khủng khiếp, rằng tôi không thể cưỡng lại nitpick. Tôi không thể giúp được. 2 xu của tôi: tuyên bố objsomeclass = someclass(); là không cần thiết. Trong phần thân của hàm tạo, objsomeclass đã được khởi tạo mặc định. objsomeclass (someclass()) dưới đây không có ý nghĩa. –

+0

Tôi đồng ý, nhưng nghĩ rằng someclass có một constructor rõ ràng.Và tôi muốn tập trung vào đối tượng được tạo ra trong constructor – yesraaj

+0

Vâng, tôi biết nó chỉ là một ví dụ. Đó là lý do tại sao tôi gọi nó là nitpicking. Cơ sở xây dựng BTW() có thể được công khai :) –

Trả lời

35

Có nó sẽ rò rỉ bộ nhớ. Khi constructor ném, không có destructor nào được gọi (trong trường hợp này bạn không hiển thị một destructor để giải phóng các đối tượng được cấp phát động, nhưng cho phép giả sử bạn có một).

Đây là lý do chính để sử dụng con trỏ thông minh - vì những người sáng tạo thông minh là những đối tượng chính thức, họ sẽ nhận được những kẻ phá hoại được gọi trong ngăn xếp của ngoại lệ và có cơ hội giải phóng bộ nhớ.

Nếu bạn sử dụng một cái gì đó giống như Boost của scoped_ptr <> mẫu, lớp học của bạn có thể trông giống như:

class base{ 
    int a; 
    scoped_ptr<int> pint; 
    someclass objsomeclass; 
    scoped_ptr<someclass> psomeclass; 
    base() : 
     pint(new int), 
     objsomeclass(someclass()), 
     psomeclass(new someclass()) 

    { 
     throw "constructor failed"; 
     a = 43; 
    } 
} 

Và bạn sẽ không có rò rỉ bộ nhớ (và dtor mặc định cũng sẽ dọn dẹp cấp phát bộ nhớ động) .


Tóm lại (và hy vọng này cũng trả lời cho câu hỏi về báo cáo kết quả

base* temp = new base(); 

):

Khi một ngoại lệ được ném ra bên trong một nhà xây dựng có một số điều mà bạn nên tham lưu ý về mặt xử lý đúng phân bổ nguồn lực có thể đã xảy ra trong việc xây dựng bị hủy bỏ của đối tượng:

  1. destructor cho đối tượng đang được xây dựng sẽ không được gọi là.
  2. destructors cho các đối tượng thành viên có trong lớp của đối tượng đó sẽ được gọi là
  3. bộ nhớ cho đối tượng đang được xây dựng sẽ được giải phóng.

Điều này có nghĩa rằng nếu đối tượng của bạn sở hữu nguồn tài nguyên, bạn có 2 phương pháp có sẵn để làm sạch những nguồn lực mà có thể đã được mua lại khi các nhà xây dựng ném:

  1. bắt ngoại lệ, giải phóng tài nguyên, sau đó trở lại. Điều này có thể khó khăn để có được chính xác và có thể trở thành một vấn đề bảo trì.
  2. sử dụng các đối tượng để quản lý thời gian tồn tại tài nguyên (RAII) và sử dụng các đối tượng đó làm thành viên. Khi hàm khởi tạo cho đối tượng của bạn ném ra một ngoại lệ, các đối tượng thành viên sẽ có các trình mô tả được gọi và sẽ có cơ hội giải phóng tài nguyên mà chúng có trách nhiệm.
+0

Không phải là kéo mạnh trong Boost chỉ cần đi có được quản lý bộ nhớ khá ngớ ngẩn? –

+0

Có lẽ, nhưng scoped_ptr là trong TR1 và sẽ được trong C + + 09, do đó, nó là cái gì đó nên được học anyway. Và một phần của Boost có scoped_ptr chỉ là một loạt các tiêu đề. Cuối cùng, bạn có thể sử dụng auto_ptr thay cho ví dụ đơn giản này, nhưng auto_ptr có lẽ là một thứ cần tránh. –

+0

Liệu người quản lý của lớp cơ sở sẽ được gọi ngay cả khi tôi có một lần? điều gì sẽ xảy ra với dòng dưới đây base * temp = new base(); – yesraaj

-2

Mọi thứ bạn cần "mới" cần xóa hoặc bạn sẽ bị rò rỉ bộ nhớ. Vì vậy, hai dòng lệnh sau:

psomeclass = new someclass(); 
pint = new int(); 

có gây ra rò rỉ bộ nhớ, bởi vì bạn cần làm:

delete pint; 
delete psomeclass; 

Trong một khối finally để tránh chúng bị rò rỉ.

Ngoài ra, dòng này:

base temp = base(); 

là không cần thiết. Bạn chỉ cần làm:

base temp; 

Việc thêm "= base()" là không cần thiết.

+1

Không có điều như một khối "cuối cùng" trong C++ –

+0

Đúng, bạn có thể hoặc không có quyền truy cập vào nó tùy thuộc vào hương vị C++ của bạn - nếu không , bạn sẽ phải đảm bảo phân bổ bị xóa bất kể đường dẫn mã được thực hiện. – Colen

+1

Nhận xét của bạn về việc khởi tạo thêm là sai. Đối tượng kết quả sẽ chỉ được khởi tạo một lần và sẽ không được sao chép. –

0

Có, mã đó sẽ bị rò rỉ bộ nhớ. Các khối bộ nhớ được cấp phát bằng cách sử dụng "mới" không được giải phóng khi ngoại lệ được nâng lên. Đây là một phần của động lực đằng sau RAII.

Để tránh rò rỉ bộ nhớ, hãy thử một cái gì đó như thế này:

psomeclass = NULL; 
pint = NULL; 
/* So on for any pointers you allocate */ 

try { 
    objsomeclass = someclass(); 
    psomeclass = new someclass(); 
    pint = new int(); 
    throw "constructor failed"; 
    a = 43; 
} 
catch (...) 
{ 
    delete psomeclass; 
    delete pint; 
    throw; 
} 

+0

thay vì sử dụng con trỏ bằng cách sử dụng đối tượng (con trỏ thông minh) sẽ làm cho mọi thứ tốt hơn? Kể từ khi bao giờ một ngoại lệ được ném vào một khối đối tượng tự động sẽ bị xóa. – yesraaj

+0

con trỏ thông minh tốt hơn. Cũng thay thế 'nâng cao'; với 'ném'; Để trả lại ngoại lệ hiện tại. –

0

Nếu bạn ném vào một constructor, bạn nên dọn dẹp tất cả những gì đến trước khi cuộc gọi để ném. Nếu bạn đang sử dụng thừa kế hoặc ném vào một destructor, bạn thực sự không nên. Các hành vi là lẻ (không có tiêu chuẩn của tôi tiện dụng, nhưng nó có thể là không xác định?).

+0

Không chắc chắn nếu nó thực sự không xác định, nhưng chắc chắn là rất nguy hiểm vì destructors được gọi là trong stack unwinding trên trường hợp của một ngoại lệ lớn lên. Nếu bạn đưa ra một ngoại lệ * trong khi * một lỗi khác đã được nâng lên, mọi thời gian chạy C++ mà tôi biết sẽ chấm dứt ứng dụng. –

+0

Một ngoại lệ chưa được khai thác trong một destructor lớn lên trong quá trình xử lý ngoại lệ gây ra std :: terminate() được gọi, theo mặc định gọi std :: abort(). Hành vi mặc định có thể bị ghi đè. – KTC

+0

mặc dù hành vi mặc định có thể bị ghi đè, phiên bản của bạn vẫn không thể quay lại ứng dụng, nó vẫn phải thoát. –

5

Cả hai mục mới sẽ bị rò rỉ.

Gán địa chỉ của các đối tượng được tạo thành tên là con trỏ thông minh để nó sẽ bị xóa bên trong trình phá hủy con trỏ thông minh nhận cuộc gọi khi ngoại lệ được ném - (RAII).

class base { 
    int a; 
    boost::shared_ptr<int> pint; 
    someclass objsomeclass; 
    boost::shared_ptr<someclass> psomeclass; 

    base() : 
     objsomeclass(someclass()), 
     boost::shared_ptr<someclass> psomeclass(new someclass()), 
     boost::shared_ptr<int> pint(new int()) 
    { 
     throw "constructor failed"; 
     a = 43; 
    } 
}; 

Bây giờ psomeclass & pint destructors sẽ được gọi khi chồng thư giãn khi ngoại lệ được ném vào các nhà xây dựng, và những destructors sẽ deallocate bộ nhớ được phân bổ.

int main(){ 
    base *temp = new base(); 
} 

Để phân bổ bộ nhớ thông thường sử dụng (không plcaement) mới, bộ nhớ được cấp bởi toán tử mới được giải phóng tự động nếu người xây dựng ném ngoại lệ. Xét về lý do tại sao bận tâm giải phóng các thành viên cá nhân (trả lời bình luận cho câu trả lời của Mike B), việc giải phóng tự động chỉ áp dụng khi một ngoại lệ được ném vào một hàm tạo của một đối tượng đang được cấp phát mới, chứ không phải trong các trường hợp khác. Ngoài ra, bộ nhớ được giải phóng là bộ nhớ được cấp phát cho các thành viên đối tượng, không phải bất kỳ bộ nhớ nào bạn có thể đã phân bổ nói bên trong hàm tạo. I E.Nó sẽ giải phóng bộ nhớ cho các biến thành viên một, pint, objsomeclass, và psomeclass, nhưng không phải là bộ nhớ được phân bổ từ SomeClass mới()int mới().

+0

shared_ptr <> là quá mức cần thiết nếu bạn sở hữu đối tượng và sẽ không bao giờ cung cấp quyền sở hữu được chia sẻ.Đơn giản hóa với tiêu chuẩn :: auto_ptr <> –

+0

// Đã thay đổi câu hỏi thành số base * temp = new base(); – yesraaj

+0

Và tăng cường :: scoped_ptr <> có thể còn tốt hơn auto_ptr <>, nó có thể có các giun riêng. –

-3

bạn cần phải xoá psomeclass ... nó không cần thiết để làm sạch các số nguyên ...

RWendi

+0

Bạn có thể xây dựng Dave Moore không? Có phải về phần "không cần thiết để làm sạch phần nguyên" không? Lý do đằng sau điều này là con trỏ bộ nhớ Int không tốn nhiều chi phí so với con trỏ bộ nhớ lớp, đó là lý do tại sao tôi nói nó không cần thiết để làm sạch nó. – RWendi

+0

Cả hai đều bị rò rỉ; chi phí không phải là một vấn đề. Câu hỏi đặt ra là liệu nó có bị rò rỉ hay không. Và nếu đoạn mã đó thực hiện hàng ngàn hoặc hàng triệu lần, thì chi phí nhỏ đó sẽ tăng thêm. Ngay cả khi "chi phí" có liên quan, nó không phải là kích thước * của con trỏ * tạo nên sự khác biệt, mà đúng hơn là kích thước của thực thể được trỏ tới. Ví dụ, có thể cho sizeof (someclass) == sizeof (int). Và bạn không xóa con trỏ - bạn đang xóa thực thể được chỉ định. –

1

Tôi tin rằng câu trả lời đầu là sai và vẫn sẽ bị rò rỉ bộ nhớ. Hàm hủy cho các thành viên lớp sẽ không được gọi nếu hàm tạo đưa ra một ngoại lệ (vì nó không bao giờ hoàn thành khởi tạo của nó và có lẽ một số thành viên chưa bao giờ đạt được các hàm tạo của chúng). destructors của họ chỉ được gọi trong cuộc gọi destructor của lớp. Điều đó chỉ có ý nghĩa.

Chương trình đơn giản này minh họa nó.

#include <stdio.h> 


class A 
{ 
    int x; 

public: 
    A(int x) : x(x) { printf("A constructor [%d]\n", x); } 
    ~A() { printf("A destructor [%d]\n", x); } 
}; 


class B 
{ 
    A a1; 
    A a2; 

public: 
    B() 
    : a1(3), 
     a2(5) 
    { 
     printf("B constructor\n"); 
     throw "failed"; 
    } 
    ~B() { printf("B destructor\n"); } 
}; 


int main() 
{ 
    B b; 

    return 0; 
} 

Với kết quả như sau (sử dụng g ++ 4.5.2):

A constructor [3] 
A constructor [5] 
B constructor 
terminate called after throwing an instance of 'char const*' 
Aborted 

Nếu constructor của bạn bị lỗi partway thì đó là trách nhiệm của bạn để đối phó với nó. Tệ hơn nữa, ngoại lệ có thể bị ném ra khỏi hàm tạo của lớp cơ sở của bạn! Cách để đối phó với những trường hợp này là sử dụng một "chức năng thử khối" (nhưng ngay cả sau đó bạn phải cẩn thận mã sự hủy diệt của đối tượng khởi tạo một phần của bạn).

Cách tiếp cận chính xác cho vấn đề của bạn sau đó sẽ là một cái gì đó như thế này:

#include <stdio.h> 


class A 
{ 
    int x; 

public: 
    A(int x) : x(x) { printf("A constructor [%d]\n", x); } 
    ~A() { printf("A destructor [%d]\n", x); } 
}; 


class B 
{ 
    A * a1; 
    A * a2; 

public: 
    B() 
    try // <--- Notice this change 
    : a1(NULL), 
     a2(NULL) 
    { 
     printf("B constructor\n"); 
     a1 = new A(3); 
     throw "fail"; 
     a2 = new A(5); 
    } 
    catch (...) { // <--- Notice this change 
     printf("B Cleanup\n"); 
     delete a2; // It's ok if it's NULL. 
     delete a1; // It's ok if it's NULL. 
    } 

    ~B() { printf("B destructor\n"); } 
}; 


int main() 
{ 
    B b; 

    return 0; 
} 

Nếu bạn chạy nó, bạn sẽ nhận được kết quả mong muốn mà chỉ có các đối tượng được phân bổ đều bị phá hủy và giải phóng.

B constructor 
A constructor [3] 
B Cleanup 
A destructor [3] 
terminate called after throwing an instance of 'char const*' 
Aborted 

Bạn vẫn có thể làm việc với con trỏ được chia sẻ thông minh nếu muốn, với bản sao bổ sung. Viết một hàm tạo tương tự như sau:

class C 
{ 
    std::shared_ptr<someclass> a1; 
    std::shared_ptr<someclass> a2; 

public: 
    C() 
    { 
     std::shared_ptr<someclass> new_a1(new someclass()); 
     std::shared_ptr<someclass> new_a2(new someclass()); 

     // You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack. 
     a1 = new_a1; 
     a2 = new_a2; 
    } 
} 

Chúc may mắn, Tzvi.

+0

Trường hợp ngoại lệ trong ví dụ đầu tiên của bạn không bị bắt, do đó, việc bỏ thư giãn không xảy ra và không có trình phá hủy nào được gọi. Nếu bạn quấn 'B b;' trong một lần thử thì các trình phá hủy được gọi như mong đợi. –