2012-01-18 41 views
19

Tôi đã đọc một cách đơn giản rằng khi bạn sử dụng vị trí mới thì bạn phải gọi hàm hủy theo cách thủ công.Làm cách nào để giải phóng bộ nhớ được cấp phát theo vị trí mới?

xem xét mã folowing:

// Allocate memory ourself 
char* pMemory = new char[ sizeof(MyClass)]; 

// Construct the object ourself 
MyClass* pMyClass = new(pMemory) MyClass(); 

// The destruction of object is our duty. 
pMyClass->~MyClass(); 

Theo như tôi biết điều hành delete thường gọi destructor và sau đó deallocates bộ nhớ, phải không? Vậy tại sao chúng ta không sử dụng delete để thay thế?

delete pMyClass; //what's wrong with that? 

trong trường hợp đầu tiên, chúng tôi buộc phải thiết lập pMyClass để nullptr sau khi chúng tôi gọi destructor như thế này:

pMyClass->~MyClass(); 
pMyClass = nullptr; // is that correct? 

NHƯNG destructor KHÔNG deallocate bộ nhớ, phải không? Vậy đó có phải là rò rỉ bộ nhớ không?

Tôi bối rối, bạn có thể giải thích điều đó không?

+1

+1 Câu hỏi hay, tôi rất muốn hiểu điều đó. – AraK

+12

Về mặt kỹ thuật, vị trí 'mới' không cấp phát bộ nhớ. Nhưng đó là một chi tiết trong bối cảnh này. – Griwes

+1

@Griwes: +1 Có thể cho rằng đó là điểm cơ bản :) –

Trả lời

32

Sử dụng toán tử new thực hiện hai việc, nó gọi hàm operator new cấp phát bộ nhớ, sau đó sử dụng vị trí mới, để tạo đối tượng trong bộ nhớ đó. Toán tử delete gọi hàm hủy của đối tượng và sau đó gọi operator delete. Vâng, những cái tên này rất khó hiểu.

//normal version     calls these two functions 
MyClass* pMemory = new MyClass; void* pMemory = operator new(sizeof(MyClass)); 
            MyClass* pMyClass = new(pMemory) MyClass(); 
//normal version     calls these two functions 
delete pMemory;     pMyClass->~MyClass(); 
            operator delete(pMemory); 

Vì trong trường hợp của bạn, bạn đã sử dụng vị trí mới theo cách thủ công, bạn cũng cần gọi hàm hủy theo cách thủ công. Vì bạn đã cấp phát bộ nhớ theo cách thủ công, bạn cần phải phát hành bộ nhớ theo cách thủ công. Hãy nhớ rằng, nếu bạn phân bổ với new, phải có mã số delete. Luôn luôn.

Tuy nhiên, vị trí mới được thiết kế để làm việc với bộ đệm nội bộ cũng như (và các kịch bản khác), nơi mà các bộ đệm là không giao operator new, đó là lý do bạn không nên gọi operator delete trên chúng.

#include <type_traits> 

struct buffer_struct { 
    std::aligned_storage<sizeof(MyClass)>::type buffer; 
}; 
int main() { 
    buffer_struct a; 
    MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a 
    //stuff 
    pMyClass->~MyClass(); //can't use delete, because there's no `new`. 
    return 0; 
} 

Mục đích của lớp buffer_struct là tạo ra và phá hủy các lưu trữ trong bất cứ cách nào, trong khi main sẽ chăm sóc của việc xây dựng/phá hủy MyClass, lưu ý làm thế nào hai là (gần như *) hoàn toàn tách biệt với nhau .

* chúng ta phải chắc chắn việc lưu trữ phải là lớn đủ

+2

Đây là gốc của câu hỏi: "Tuy nhiên, vị trí mới được thiết kế cho bộ đệm nội bộ, bản thân chúng không được phân bổ bằng mới, đó là lý do tại sao bạn không nên gọi xóa trên chúng." Bạn không giải phóng bộ nhớ vì bạn sẽ sử dụng phần đệm đó cho một đối tượng khác cùng loại tại một thời điểm tương lai. Bạn gọi destructor để dụ có thể dọn dẹp. –

+0

Ví dụ tuyệt vời, Vì vậy, để xóa bộ đệm chúng tôi gọi: xóa [] a.buffer nếu nó sẽ là dinamicaly, và làm thế nào để chúng ta gọi destructor của MyClass sau đó? – codekiddy

+0

@codekiddy: Vì 'a.buffer' không được phân bổ động, nó không cần phải được deallocated. Trong mẫu của tôi nó được tự động cấp phát, vì vậy bộ đệm hoàn toàn tự động theo mọi cách. Mối quan tâm duy nhất là vị trí mới của lớp, và sau đó gọi destructor của lớp đó, như mã mẫu của tôi cho thấy. –

9

Một lý do đây là sai:

delete pMyClass; 

là bạn phải xóa pMemory với delete[] vì nó là một mảng:

delete[] pMemory; 

Bạn không thể làm cả hai điều trên.

Tương tự, bạn có thể hỏi tại sao bạn không thể sử dụng malloc() để cấp phát bộ nhớ, vị trí mới để tạo đối tượng và sau đó delete để xóa và giải phóng bộ nhớ. Lý do là bạn phải khớp với malloc()free(), không phải malloc()delete.

Trong thế giới thực, các cuộc gọi hủy cuộc gọi mới và rõ ràng hầu như không bao giờ được sử dụng. Chúng có thể được sử dụng trong nội bộ bởi việc thực hiện Thư viện chuẩn (hoặc cho các chương trình cấp hệ thống khác như được ghi trong các bình luận), nhưng các lập trình viên bình thường không sử dụng chúng. Tôi chưa bao giờ sử dụng các thủ đoạn như vậy cho mã sản xuất trong nhiều năm làm C++.

+0

Chỉ cần đề cập đến: vị trí 'mới' cũng được sử dụng thường xuyên khi thực hiện phát triển hệ điều hành hoặc một số điều tương tự trong C++. – Griwes

+0

Xin chào, vì vậy chúng tôi acctualy không cần phải gọi destructor manualy kể từ khi xóa [] pMemory sẽ gọi destructor và giải phóng bộ nhớ sin'it? – codekiddy

+2

@codekiddy: 'delete [] pMemory' không * không * gọi hàm hủy cho đối tượng của bạn. –

4

Bạn cần phải phân biệt giữa delete điều hành và operator delete. Đặc biệt, nếu bạn đang sử dụng vị trí mới, bạn gọi một cách rõ ràng destructor và sau đó gọi operator delete (và không phải là delete điều hành) để giải phóng bộ nhớ, tức là

X *x = static_cast<X*>(::operator new(sizeof(X))); 
new(x) X; 
x->~X(); 
::operator delete(x); 

Lưu ý rằng đây sử dụng operator delete, đó là thấp hơn lại cấp độ hơn so với nhà điều hành delete và không lo lắng về destructors (nó cơ bản là một chút như free). So sánh điều này với toán tử delete, điều này thực hiện tương đương với cách gọi hàm hủy và gọi operator delete.

Cần lưu ý rằng bạn không phải sử dụng ::operator new::operator delete để phân bổ và giải quyết bộ đệm của bạn - cho đến khi vị trí mới được quan tâm, không quan trọng bộ đệm bị xâm nhập/bị hủy như thế nào. Điểm chính là tách biệt các mối quan tâm về phân bổ bộ nhớ và tuổi thọ đối tượng.

Ngẫu nhiên, có thể ứng dụng này sẽ giống như trò chơi, nơi bạn có thể muốn phân bổ một khối bộ nhớ lớn lên phía trước để quản lý cẩn thận việc sử dụng bộ nhớ của bạn. Sau đó bạn sẽ xây dựng các đối tượng trong bộ nhớ mà bạn đã có được.

Một cách sử dụng khác có thể nằm trong bộ phân bổ đối tượng có kích thước nhỏ, cố định được tối ưu hóa.

+0

+1 cảm ơn vì đã chỉ ra điều đó! – codekiddy

3

Có thể dễ hiểu hơn nếu bạn tưởng tượng việc xây dựng một số đối tượng MyClass trong một số một khối.

Trong trường hợp đó, nó sẽ đi một cái gì đó như:

  1. Phân bổ một khối khổng lồ của bộ nhớ sử dụng char mới [10 * sizeof (MyClass)] hoặc malloc (10 * sizeof (MyClass))
  2. Sử dụng vị trí mới để xây dựng mười đối tượng MyClass trong bộ nhớ đó.
  3. Làm điều gì đó.
  4. Gọi trình phá hủy của từng đối tượng của bạn
  5. Giải quyết khối bộ nhớ lớn bằng cách xóa [] hoặc miễn phí().

Đây là loại điều bạn có thể làm gì nếu bạn viết một trình biên dịch, hoặc một hệ điều hành vv

Trong trường hợp này, tôi hy vọng đó là rõ ràng lý do tại sao bạn cần "destructor" riêng và "xóa" các bước, bởi vì không có lý do gì khiến bạn sẽ xóa cuộc gọi.Tuy nhiên, bạn nên deallocate bộ nhớ tuy nhiên bạn thường làm điều đó (miễn phí, xóa, không làm gì cho một mảng tĩnh khổng lồ, thoát bình thường nếu mảng là một phần của đối tượng khác, v.v.) và nếu bạn không nó sẽ bị rò rỉ. Cũng lưu ý như Greg nói, trong trường hợp này, bạn không thể sử dụng xóa, bởi vì bạn đã phân bổ mảng với [] mới, do đó bạn cần phải sử dụng delete []. Cũng cần lưu ý rằng bạn cần giả định rằng bạn không ghi đè xóa cho MyClass, nếu không nó sẽ làm điều gì đó hoàn toàn khác mà gần như chắc chắn không tương thích với "mới".

Vì vậy, tôi nghĩ bạn không muốn gọi "xóa" như bạn mô tả, nhưng nó có thể hoạt động không? Tôi nghĩ rằng đây là cơ bản cùng một câu hỏi như "Tôi có hai loại không liên quan mà không có destructors. Tôi có thể mới một con trỏ đến một loại, sau đó xóa bộ nhớ thông qua một con trỏ đến một loại?" Nói cách khác, "trình biên dịch của tôi có một danh sách lớn của tất cả các công cụ được phân bổ, hoặc nó có thể làm những việc khác nhau cho các loại khác nhau".

Tôi sợ rằng tôi không chắc chắn. Đọc spec nó nói:

5.3.5 ... Nếu loại tĩnh của các toán hạng [của các nhà điều hành xóa] là khác biệt so với loại động của nó, loại tĩnh sẽ là một lớp cơ sở của toán hạng của loại động và loại tĩnh sẽ có một destructor ảo hoặc hành vi không xác định.

Tôi nghĩ điều đó có nghĩa là "Nếu bạn sử dụng hai loại không liên quan, nó không hoạt động (bạn có thể xóa đối tượng lớp một cách đa hình thông qua trình phá hủy ảo)."

Vì vậy, không, đừng làm vậy. Tôi nghi ngờ nó thường có thể làm việc trong thực tế, nếu trình biên dịch chỉ nhìn vào địa chỉ và không phải là loại (và không phải loại là một lớp đa thừa kế, mà sẽ mangle địa chỉ), nhưng không thử nó.

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