Phải enable_ Shared_from_this là lớp cơ sở đầu tiên?


8

Lớp học của tôi kế thừa từ nhiều cơ sở, một trong số đó là std::enable_shared_from_this. Nó phải là cơ sở đầu tiên?

Giả sử mã ví dụ sau:

struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};

std::make_shared<C>(); 

Khi ~A()~B()chạy, tôi có thể chắc chắn rằng bộ lưu trữ nơi Cvẫn còn tồn tại không?


1
Tại sao bạn cảm thấy thứ tự của sự hủy diệt? Kẻ hủy diệt std::enable_shared_from_thiskhông làm gì nhiều. Ví dụ bạn có vẻ OK với tôi (giả sử bạn không cố gắng để làm bất cứ điều gì thông minh trong ~A~B, như xuống đúc thisđể C*)
Igor Tandetnik

1
@SM Đây không phải là vấn đề truy cập. Tôi biết enable_shared_from_thisphải là một cơ sở rõ ràng dễ tiếp cận. Trong ví dụ của tôi, nó là. Clà một cấu trúc. Nó kế thừa công khai.
Filipp

1
Có, nhưng quyền truy cập cơ sở được xác định bởi việc thực hiện kế thừa, chứ không phải điều được kế thừa từ đó. Tôi có thể thay đổi ví dụ của tôi nếu bạn muốn. Mã thực tế mà nó dựa trên sử dụng classpublic. Tôi chọn structví dụ để tránh gõ.
Filipp

4
Do đó, tiêu chuẩn: " [produc.smartptr.weak.dest] ~weak_ptr(); Hiệu ứng: Phá hủy weak_ptrđối tượng này nhưng không có tác dụng đối với đối tượng mà con trỏ được lưu trữ của nó trỏ tới." Nhấn mạnh mỏ.
Igor Tandetnik

1
@Filipp Tuổi thọ của đối tượng được lưu trữ kết thúc khi lần shared_ptrchết cuối cùng . Ngay cả khi weak_ptrkhối điều khiển không bị giải phóng, tôi cũng không nghĩ nó quan trọng.
HolyBlackCat

Câu trả lời:


1

Khi ~A()~B()chạy, tôi có thể chắc chắn rằng bộ lưu trữ nơi Cvẫn còn tồn tại không?

Tất nhiên! Thật khó để sử dụng một lớp cơ sở cố gắng giải phóng bộ nhớ của chính nó (bộ nhớ nơi nó cư trú). Tôi không chắc nó thậm chí còn chính thức hợp pháp.

Việc triển khai không làm điều đó: khi a shared_ptr<T>bị phá hủy hoặc thiết lập lại, số tham chiếu (RC) cho quyền sở hữu chung Tđược giảm đi (về nguyên tử); nếu nó đạt 0 trong phần giảm, thì hủy / xóaT sẽ được bắt đầu.

Sau đó, số lượng chủ sở hữu yếu hoặc T tồn tại bị giảm (về nguyên tử), vì Tkhông còn tồn tại: chúng ta cần biết liệu chúng ta có phải là thực thể cuối cùng quan tâm đến khối điều khiển hay không; nếu phần giảm cho kết quả khác không, điều đó có nghĩa là một số weak_ptrtồn tại mà chia sẻ (có thể là 1 cổ phần hoặc 100%) quyền sở hữu của khối kiểm soát và giờ đây họ chịu trách nhiệm cho việc phân bổ.

Dù bằng cách nào, sự suy giảm nguyên tử tại một thời điểm nào đó sẽ kết thúc với giá trị bằng 0, cho người đồng sở hữu cuối cùng.

Ở đây không có chủ đề, không có chủ nghĩa không xác định, và rõ ràng cuối cùng weak_ptr<T>đã bị phá hủy trong quá trình phá hủy C. (Giả định bất thành văn trong câu hỏi của bạn là không có ai khác weak_ptr<T>được giữ lại.)

Phá hủy luôn xảy ra theo thứ tự chính xác . Khối điều khiển được sử dụng để hủy, vì không shared_ptr<T>biết (nói chung) mà hàm hủy (có khả năng không ảo) của lớp dẫn xuất nhất (có khả năng khác nhau) sẽ gọi . (Khối điều khiển cũng biết không phân bổ bộ nhớ cho số lượng chia sẻ đạt đến 0 cho make_shared.)

Sự khác biệt thực tế duy nhất giữa các triển khai dường như là về các chi tiết tốt của hàng rào bộ nhớ và tránh một số hoạt động nguyên tử trong các trường hợp phổ biến.


Đây là câu trả lời tôi đang tìm kiếm! Cảm ơn bạn! Điều quan trọng là đối tượng còn sống thực sự được tính là một mục đích sử dụng yếu. Đó là weak_count1 cho một đối tượng đã được make_shared-ed ngay cả khi không có weak_ptrs. Chỉ phát hành các shared_ptrgiảm đầu tiên use_count. Nếu nó trở thành 0, đối tượng (nhưng không phải khối điều khiển) bị phá hủy. Sau đó weak_count được giảm dần và nếu 0 khối điều khiển bị hủy + giải phóng. Một đối tượng kế thừa từ enable_shared_from_thisbắt đầu bằng weak_count= 2. Một giải pháp tuyệt vời của những người triển khai STL, như mong đợi.
Filipp

Chỉ cần một nitpick pedantic: STL là Thư viện mẫu tiêu chuẩn, ngoại trừ các tạo tác lịch sử (HP STL hoặc SGI STL) chỉ được xác định không chính thức; đó là về các loại tuân theo các yêu cầu của Container, Iterators và "thuật toán" làm việc trên các loại đó. STL không bị giới hạn nghiêm ngặt đối với các mẫu, vì nó sử dụng một vài lớp không phải mẫu (f.ex. random_access_iterator_tag). Có thỏa thuận không chính thức để gọi bất cứ điều gì liên quan đến một phần container của STL. tl; dr: Không phải tất cả các mẫu trong lib std đều là một phần của STL và không phải tất cả các mẫu không nằm ngoài nó.
tò mò

5

Khi ~ A () và ~ B () chạy, tôi có thể chắc chắn rằng bộ lưu trữ nơi C sống vẫn còn không?

Không, và thứ tự của các lớp cơ sở là không liên quan. Ngay cả việc sử dụng (hoặc không) của enable_spl_from_this cũng không liên quan.

Khi một đối tượng C bị phá hủy (tuy nhiên điều đó xảy ra), ~C()sẽ được gọi trước cả hai ~A()~B(), vì đó là cách mà các hàm hủy cơ sở hoạt động. Nếu bạn cố gắng "tái cấu trúc" đối tượng C trong cả trường hủy cơ sở và trường truy cập trong đó, thì các trường đó đã bị hủy, do đó bạn sẽ có hành vi không xác định.


Không trả lời câu hỏi của tôi. Không nơi nào tôi đề cập đến việc cố gắng "tái cấu trúc" bất kỳ C. Câu trả lời nào phải là một trong " enable_shared_from_thiscó thể xuất hiện ở bất kỳ đâu trong danh sách cơ sở, việc triển khai được yêu cầu để giải phóng bộ nhớ sau khi phá hủy toàn bộ đối tượng, bất kể nó thừa hưởng như thế nào enable_shared_from_this" hoặc "Nó phải là cơ sở đầu tiên, kế thừa bất cứ nơi nào khác là UB "hoặc" Hành vi này không được chỉ định hoặc chất lượng thực hiện ".
Filipp

@Filipp: Câu trả lời là sự kết hợp - chúng có thể xuất hiện ở bất cứ đâu và bất kể, việc triển khai là giải phóng bộ nhớ miễn phí cho một phần của đối tượng sau khi phá hủy phần đó của đối tượng (và trước khi phá hủy các lớp cơ sở). Đơn giản là không có yêu cầu rằng bộ nhớ chỉ có thể được giải phóng sau khi phá hủy toàn bộ đối tượng, bất kể.
Chris Dodd

-1

Nếu bạn tạo một đối tượng c loại C, với các cơ sở A, B và bộ đếm tham chiếu thông qua kế thừa từ cơ sở enable_shared_from_this<T>, trước hết bộ nhớ được phân bổ cho toàn bộ đối tượng kết quả, bao gồm các cơ sở nói chung và cơ sở enable_shared_from_this<T>. Đối tượng sẽ không bị hủy cho đến khi chủ sở hữu cuối cùng (còn gọi là shared_ptr) từ bỏ quyền sở hữu. Tại thời điểm đó ~ enable_ Shared ..., ~ B và ~ A sẽ được chạy sau ~ C. Bộ nhớ được phân bổ hoàn chỉnh vẫn được đảm bảo ở đó cho đến khi bộ hủy cuối cùng ~ A được chạy. Sau khi ~ A được chạy, bộ nhớ đối tượng hoàn chỉnh sẽ được giải phóng trong một cú trượt. Để trả lời câu hỏi của bạn:

Khi ~ A () và ~ B () chạy, tôi có thể chắc chắn rằng bộ lưu trữ nơi C sống vẫn còn không?

Đúng vậy, mặc dù bạn không thể truy cập nó một cách hợp pháp, nhưng tại sao bạn cần phải biết? Vấn đề nào bạn đang cố gắng tránh?


Những gì bạn viết là đúng, nhưng không trả lời câu hỏi của tôi. Tất nhiên các hàm hủy lớp cơ sở chạy sau lớp dẫn xuất. Tôi đang tự hỏi liệu triển khai của shared_ptr, weak_ptrenable_shared_from_thisđược yêu cầu phải giữ cho bộ nhớ khoảng thời gian đủ dài để làm cho an toàn này, ngay cả khi enable_shared_from_thiskhông phải là cơ sở đầu tiên.
Filipp

À được rồi Nhìn vào câu hỏi ban đầu của bạn: không có gì rõ ràng 'cái này' [như trong "hãy tiết kiệm" trong nhận xét trên của bạn] bạn đang cố gắng đạt được. Sẽ chỉnh sửa câu trả lời của tôi để phản ánh câu hỏi khi tôi hiểu nó vào lúc này.
Andreas_75

Làm cho điều này an toàn = kế thừa từ enable_shared_from_thissau một lớp cơ sở khác.
Filipp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.