2017-01-18 17 views
15

Tôi đã đọc ở nhiều nơi khi sử dụng make_shared<T> để tạo shared_ptr<T>, khối điều khiển của nó chứa một khối bộ nhớ đủ lớn để giữ T và sau đó đối tượng được xây dựng bên trong bộ nhớ vị trí mới. Một cái gì đó như thế này:Tại sao shared_ptr sử dụng vị trí mới

template<typename T> 
struct shared_ptr_control_block { 
    std::atomic<long> count; 
    std::atomic<long> weak_count; 
    std::aligned_storage_t<sizeof (T), alignof (T)> storage; 
}; 

Nhưng tôi là một chút nhầm lẫn tại sao chúng ta không thể chỉ có một biến thành viên với kiểu T để thay thế? Tại sao tạo bộ nhớ thô sau đó sử dụng vị trí mới? Không thể kết hợp nó trong một bước với đối tượng bình thường của loại T?

Trả lời

18

Đó là để cho phép quản lý lâu dài.

Khối điều khiển không bị hủy cho đến khi weak_count bằng không. Đối tượng storage bị hủy ngay sau khi count đạt đến 0. Điều đó có nghĩa là bạn cần gọi trực tiếp trình phá hủy của storage khi số đếm đạt đến 0 và không phải trong trình phá hủy của khối điều khiển.

Để ngăn chặn destructor của khối điều khiển gọi destructor của storage, loại thực tế của storage không được là T.

Nếu chúng tôi chỉ có số lượng tham chiếu mạnh, thì T sẽ ổn (và đơn giản hơn nhiều).


Thực tế, việc triển khai phức tạp hơn một chút so với điều này. Hãy nhớ rằng shared_ptr có thể được xây dựng bằng cách phân bổ T với new và sau đó xây dựng shared_ptr từ đó. Do đó, kiểm soát khối thực tế trông giống như:

template<typename T> 
struct shared_ptr_control_block { 
    std::atomic<long> count; 
    std::atomic<long> weak_count; 
    T* ptr; 
}; 

và những gì make_shared giao đất là:

template<typename T> 
struct both { 
    shared_ptr_control_block cb; 
    std::aligned_storage_t<sizeof (T), alignof (T)> storage; 
}; 

cb.p được thiết lập đến địa chỉ của storage. Phân bổ cấu trúc both trong make_shared có nghĩa là chúng tôi nhận được một phân bổ bộ nhớ duy nhất, thay vì hai (và phân bổ bộ nhớ là tốn kém).

Lưu ý: Tôi đã đơn giản hóa: Phải có cách để destructor shared_ptr biết khối kiểm soát có phải là một phần của both (trong trường hợp đó bộ nhớ không thể được giải phóng cho đến khi thực hiện) hay không (trong trường hợp đó có thể được giải phóng sớm hơn). Đây có thể là một cờ bool đơn giản (trong trường hợp khối điều khiển lớn hơn), hoặc bằng cách sử dụng một số bit dự phòng trong một con trỏ (không phải là di động - nhưng việc triển khai thư viện chuẩn không cần phải di chuyển). Việc triển khai có thể thậm chí là nhiều hơn phức tạp để tránh lưu trữ con trỏ tại tất cả trong trường hợp make_shared.

+0

Ah, tôi thấy bây giờ. Vì vậy, nếu chúng ta chỉ có một số tham chiếu (số đếm mạnh), thì một 'T' sẽ là đủ, đúng không? –

+0

@ZizhengTai, có –

+0

Có thể đáng nói đến là make_shared kết hợp khối điều khiển và đối tượng để giảm số lượng phân bổ bộ nhớ cần thiết. chỉ đơn giản là xây dựng một shared_ptr yêu cầu một phân bổ cho đối tượng đang được chia sẻ và một phân bổ cho khối điều khiển. –

7

Khi con trỏ yếu có thể sống lâu hơn đối tượng được lưu trữ, tuổi thọ của khối điều khiển có thể phải vượt quá tuổi thọ của đối tượng được lưu trữ. Nếu đối tượng được quản lý sẽ là một biến thành viên, nó chỉ có thể bị phá hủy khi khối điều khiển bị hủy (hoặc destructor sẽ được gọi hai lần).

Thực tế lưu trữ vẫn được phân bổ ngay cả sau khi đối tượng bị phá hủy thực sự có thể là bất lợi của make_shared trong hệ thống hạn chế bộ nhớ (mặc dù tôi không biết đây có phải là điều gì đó đã xảy ra không).

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