2013-02-25 41 views
14

câu hỏi cụ thể của tôi là khi thực hiện một singleton class trong C++, là có bất kỳ sự khác biệt đáng kể giữa hai bên dưới mã liên quan đến hiệu suất, vấn đề bên hoặc một cái gì đó:Heap/năng động so với cấp phát bộ nhớ tĩnh ví dụ C++ singleton lớp

class singleton 
{ 
    // ... 
    static singleton& getInstance() 
    { 
     // allocating on heap 
     static singleton* pInstance = new singleton(); 
     return *pInstance; 
    } 
    // ... 
}; 

và điều này:

class singleton 
{ 
    // ... 
    static singleton& getInstance() 
    { 
     // using static variable 
     static singleton instance; 
     return instance; 
    } 
    // ... 
}; 


(Lưu ý rằng dereferencing trong việc thực hiện đống dựa trên không ảnh hưởng đến hiệu suất, như AFAIK không có máy mã phụ tạo cho dereferencing. Nó dường như chỉ là vấn đề cú pháp để phân biệt với con trỏ)

UPDATE:.

Tôi đã có câu trả lời thú vị và ý kiến ​​mà tôi cố gắng tóm tắt chúng ở đây. (Đọc câu trả lời chi tiết được khuyến khích cho những người quan tâm.):

  • Trong singleton sử dụng tĩnh biến địa phương, các destructor lớp được tự động gọi tại thời điểm thôi quá trình, trong khi đó trong việc phân bổ trường hợp động , bạn có để quản lý sự phá hủy đối tượng một cách nào đó vào lúc nào đó, ví dụ bằng cách sử dụng con trỏ thông minh:
static singleton& getInstance() { 
     static std::auto_ptr<singleton> instance (new singleton()); 
     return *instance.get(); 
    } 
  • Các singleton sử dụng phân bổ động "lazier" hơn biến singleton tĩnh, như trong trường hợp sau, bộ nhớ cần thiết cho các đối tượng singleton là (luôn luôn ?) được đặt trước khi khởi động quá trình (như một phần của toàn bộ bộ nhớ cần thiết cho chương trình tải) và chỉ gọi của nhà xây dựng singleton được hoãn lại là getInstance() thời gian gọi. Điều này có thể quan trọng khi sizeof(singleton) lớn.

  • Cả hai đều an toàn chỉ trong C++ 11. Nhưng với các phiên bản trước của C++, nó thực hiện cụ thể.

  • Trường hợp phân bổ động sử dụng một mức không giới hạn để truy cập đối tượng đơn lẻ, trong trường hợp đối tượng đơn lẻ tĩnh, địa chỉ trực tiếp của đối tượng được xác định và mã hóa cứng tại thời gian biên dịch.


P.S .: Tôi đã sửa chữa các thuật ngữ tôi đã sử dụng trong bài đăng ban đầu theo câu trả lời của @ TonyD của.

+0

Bạn đã so sánh lắp ráp được tạo cho cả hai? –

+0

No. Nếu bạn có nghĩa là tạo ra lắp ráp cho hai triển khai khác nhau, họ rõ ràng là khác nhau, như một trong những phân bổ trên heap và một trong những hiện một thời gian tải/gọi khởi tạo. Nếu bạn có nghĩa là tạo ra lắp ráp cho dereferencing, không, tôi đã không so sánh. Tôi đoán vậy. –

Trả lời

7
  • phiên bản new rõ ràng là cần phải cấp phát bộ nhớ tại thời gian chạy, trong khi phiên bản không phải con trỏ có bộ nhớ phân bổ tại thời gian biên dịch (nhưng cả hai cần phải làm như xây dựng giống nhau)

  • các new phiên bản sẽ không gọi destructor của đối tượng tại thời điểm thôi chương trình, nhưng phiên bản không new sẽ: bạn có thể sử dụng một con trỏ thông minh để điều chỉnh này

    • bạn cần phải cẩn thận rằng một số đối tượng tĩnh/namespace-phạm vi destructors không gọi singleton của bạn sau khi destructor của thể hiện tĩnh của nó đã chạy ... nếu bạn lo lắng về điều này, bạn có lẽ nên đọc thêm một chút về cuộc đời Singleton và cách tiếp cận để quản lý chúng. Thiết kế hiện đại C++ của Andrei Alexandrescu có cách điều trị rất dễ đọc.
  • trong C++ 03, nó được triển khai thực hiện cho dù chủ đề có an toàn hay không. (Tôi tin rằng GCC có xu hướng được, trong khi Visual Studio có xu hướng không phải để xác nhận/đánh giá đúng.)

  • dưới C++ 11, an toàn: 6.7.4 "Nếu điều khiển nhập đồng thời trong khi biến đang được khởi tạo, việc thực thi đồng thời sẽ đợi để hoàn thành việc khởi tạo. " (sans đệ quy).

tái thảo luận thời gian biên dịch so với thời gian chạy phân bổ & khởi

Từ cách bạn diễn đạt tóm tắt của bạn và một vài ý kiến, tôi nghi ngờ bạn không hoàn toàn hiểu được một khía cạnh tinh tế của việc phân bổ và khởi động của các biến tĩnh ....

Giả sử chương trình của bạn có 3 địa phương tĩnh 32-bit int s - a, bc - trong các chức năng khác nhau: biên dịch r có khả năng biên dịch một hệ nhị phân cho trình tải hệ điều hành để lại 3x32-bit = 12 byte bộ nhớ cho các số liệu thống kê đó. Trình biên dịch quyết định những gì bù đắp cho mỗi biến đó là: nó có thể đặt a tại offset 1000 hex trong phân đoạn dữ liệu, b tại 1004 và c ở 1008. Khi chương trình thực thi, trình tải hệ điều hành không cần cấp phát bộ nhớ cho mỗi một cách riêng biệt - tất cả những gì nó biết là tổng số 12 byte, có thể hoặc không được yêu cầu cụ thể là 0-initialise, nhưng nó có thể muốn thực hiện để đảm bảo quá trình không thể nhìn thấy nội dung bộ nhớ còn sót lại từ chương trình của người dùng. Các hướng dẫn về mã máy trong chương trình thường sẽ mã hóa khó khăn các khoảng cách 1000, 1004, 1008 để truy cập vào a, bc - vì vậy không cần phân bổ các địa chỉ đó vào thời gian chạy.

động cấp phát bộ nhớ là khác nhau ở chỗ các con trỏ (nói p_a, p_b, p_c) sẽ được cung cấp địa chỉ tại thời gian biên dịch như vừa mô tả, nhưng bổ sung:

  • các nhọn để nhớ (mỗi a , bc) phải được tìm thấy tại thời gian chạy (thường là khi hàm tĩnh đầu tiên thực hiện nhưng trình biên dịch được phép thực hiện sớm hơn theo nhận xét của tôi về câu trả lời khác) và
    • nếu có quá ít bộ nhớ curre ntly được quy trình bởi Hệ điều hành để phân bổ động thành công, khi đó thư viện chương trình sẽ yêu cầu hệ điều hành cho nhiều bộ nhớ hơn (ví dụ: sử dụng sbreak()) - mà hệ điều hành thông thường sẽ quét sạch vì những lý do an ninh
    • các địa chỉ năng động được phân bổ cho mỗi a, bc phải được sao chép lại vào con trỏ p_a, p_bp_c.

Cách tiếp cận năng động này rõ ràng là phức tạp hơn.

+0

Điểm hay. Bởi "cấp phát bộ nhớ tại thời gian biên dịch", bạn có nghĩa là không gian bộ nhớ cần thiết được đặt trước tại thời gian tải và liên kết, nhưng việc khởi tạo được hoãn lại theo thời gian gọi hàm không? (Nếu không, điểm đầu tiên của bạn có vẻ sai) –

+0

Tôi vừa mới nhận thấy [báo giá của bạn] (http://stackoverflow.com/questions/15062767/heap-dynamic-vs-static-memory-allocation-for-c-singleton-class -instance/15063036 # comment21179656_15062905) từ C++ 11 6.7.4. Nhưng những gì về C++ 03 hoặc phiên bản cũ hơn? –

+0

@MassoodKhaari: Phân bổ lại: có, bộ nhớ quyết định (số lượng cần thiết, phân đoạn, bù đắp) cho số liệu thống kê được thực hiện tại thời gian biên dịch và hình ảnh nhị phân sẽ cho biết đủ (ví dụ: tổng kích thước vùng bộ nhớ) cho trình tải hệ điều hành đặt bộ nhớ sang một bên. Re concurrency - theo câu trả lời của tôi - nó được thực hiện xác định (nếu ở tất cả) ... C++ 03 Standard không đề cập đến chủ đề, do đó, nó đã được thực hiện để quyết định có và làm thế nào để hỗ trợ họ. –

2

Sự khác biệt chính là sử dụng một đối tượng địa phương static đối tượng sẽ bị hủy khi đóng chương trình, thay vào đó các đối tượng phân bổ heap sẽ bị hủy bỏ mà không bị hủy. Lưu ý rằng trong C++ nếu bạn khai báo một biến tĩnh bên trong một hàm, nó sẽ được khởi tạo lần đầu tiên bạn nhập phạm vi, không bắt đầu chương trình (như nó xảy ra thay cho các biến thời gian tĩnh toàn cầu).

Nói chung trong những năm qua tôi đã chuyển từ sử dụng khởi tạo lười biếng sang khởi tạo điều khiển rõ ràng vì khởi động chương trình và tắt máy là các giai đoạn tinh tế và khá khó gỡ lỗi. Nếu lớp học của bạn không làm gì phức tạp và không thể thất bại (ví dụ:nó chỉ là một đăng ký) sau đó ngay cả khởi tạo lười biếng là tốt ... nếu không được kiểm soát sẽ giúp bạn tiết kiệm khá nhiều vấn đề.

Chương trình gặp sự cố trước khi nhập hướng dẫn đầu tiên main hoặc sau khi thực hiện lệnh cuối cùng của main khó gỡ lỗi hơn.

Một vấn đề khác của việc sử dụng xây dựng lười biếng của người độc thân là nếu mã của bạn là đa luồng, bạn phải chú ý đến nguy cơ có chủ đề đồng thời khởi tạo singleton cùng một lúc. Việc khởi tạo và tắt máy trong một ngữ cảnh luồng đơn giản hơn.

+0

Vâng, tôi cũng đang sử dụng đơn cho các đối tượng khá đơn giản. Nhưng nó có vẻ thú vị; bạn có thể đề cập đến một số cách để thực hiện "khởi tạo kiểm soát rõ ràng" không? –

+0

Và tốt đẹp để chỉ ra vấn đề hủy diệt. Tôi đã không nhận ra điều đó. Vì vậy trong trường hợp này, khởi tạo tĩnh có vẻ là một lựa chọn tốt hơn. –

+0

"nó sẽ được khởi tạo lần đầu tiên bạn nhập phạm vi" - đôi khi/C++ 11 6.7.4: "Việc triển khai được phép thực hiện khởi tạo sớm các biến phạm vi khối khác với thời gian lưu trữ tĩnh hoặc trong cùng một thời điểm các điều kiện thực thi được phép khởi tạo tĩnh một biến với thời gian lưu trữ tĩnh hoặc luồng trong phạm vi không gian tên (3.6.2). hoàn tất quá trình khởi tạo. " –

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