Lợi thế của việc sử dụng std::unique_ptr<T>
(ngoài việc không phải nhớ gọi delete
hoặc gọi delete[]
rõ ràng) là nó đảm bảo rằng một con trỏ nullptr
hoặc nó trỏ đến một thể hiện hợp lệ của đối tượng (cơ sở). Tôi sẽ quay lại vấn đề này sau khi tôi trả lời câu hỏi của bạn, nhưng thông báo đầu tiên là NÊN sử dụng con trỏ thông minh để quản lý thời gian tồn tại của các đối tượng được phân bổ động.
Bây giờ, của bạn vấn đề thực sự là làm thế nào để sử dụng điều này với mã cũ của bạn .
Đề xuất của tôi là nếu bạn không muốn chuyển nhượng hoặc chia sẻ quyền sở hữu, bạn nên luôn chuyển các tham chiếu đến đối tượng. Khai báo hàm của bạn như thế này (có hoặc không const
có định tính, nếu cần):
bool func(BaseClass& ref, int other_arg) { ... }
Sau đó, người gọi, có std::shared_ptr<BaseClass> ptr
di chúc sẽ xử lý nullptr
trường hợp hoặc nó sẽ yêu cầu bool func(...)
tính toán kết quả:
if (ptr) {
result = func(*ptr, some_int);
} else {
}
Điều này có nghĩa là bất kỳ người gọi nào cũng phải hứa rằng tham chiếu là hợp lệ và nó sẽ tiếp tục hợp lệ trong suốt quá trình thực thi thân hàm.
Đây là lý do tại sao tôi thực sự tin rằng bạn không nên chuyển con trỏ thô hoặc tham chiếu đến con trỏ thông minh.
Một con trỏ thô chỉ là một địa chỉ bộ nhớ. Có thể có một trong (ít nhất) 4 nghĩa:
- Địa chỉ của một khối bộ nhớ nơi đặt đối tượng mong muốn của bạn. ( tốt )
- Địa chỉ 0x0 mà bạn có thể chắc chắn là không thể tham khảo và có thể có ngữ nghĩa là "không có gì" hoặc "không có đối tượng". ( xấu )
- Địa chỉ của một khối bộ nhớ nằm ngoài không gian địa chỉ của quy trình của bạn (tham khảo nó hy vọng sẽ khiến chương trình của bạn gặp sự cố). ( xấu xí )
- Địa chỉ của một khối bộ nhớ có thể được tham chiếu đến nhưng không chứa những gì bạn mong đợi. Có thể con trỏ đã vô tình bị sửa đổi và bây giờ nó trỏ đến một địa chỉ có thể ghi khác (của một biến hoàn toàn khác trong quy trình của bạn). Việc ghi vào vị trí bộ nhớ này đôi khi sẽ gây ra rất nhiều điều thú vị trong quá trình thực thi, bởi vì hệ điều hành sẽ không phàn nàn miễn là bạn được phép ghi ở đó. ( Zoinks! )
Việc sử dụng con trỏ thông minh một cách chính xác sẽ làm giảm bớt các trường hợp khá đáng sợ 3 và 4, thường không thể phát hiện được tại thời điểm biên dịch và bạn thường chỉ gặp phải trong thời gian chạy khi chương trình của bạn gặp sự cố hoặc xảy ra những điều không mong muốn.
Chuyển con trỏ thông minh làm đối số có hai nhược điểm: bạn không thể thay đổi const
-ness của đối tượng được trỏ mà không tạo bản sao (điều này thêm chi phí cho shared_ptr
và không thể cho unique_ptr
), và bạn vẫn còn lại với nghĩa thứ hai ( nullptr
).
Tôi đánh dấu trường hợp thứ hai là ( xấu ) từ góc độ thiết kế. Đây là một lập luận tế nhị hơn về trách nhiệm.
Hãy tưởng tượng ý nghĩa của nó khi một hàm nhận được nullptr
làm tham số của nó. Đầu tiên nó phải quyết định xem phải làm gì với nó: sử dụng một giá trị "ma thuật" thay cho đối tượng bị mất tích? thay đổi hành vi hoàn toàn và tính toán một cái gì đó khác (mà không yêu cầu đối tượng)? hoảng sợ và ném một ngoại lệ? Hơn nữa, điều gì sẽ xảy ra khi hàm nhận 2, 3 hoặc thậm chí nhiều đối số hơn bởi con trỏ thô? Nó phải kiểm tra từng người trong số họ và điều chỉnh hành vi của nó cho phù hợp. Điều này bổ sung một cấp độ hoàn toàn mới trên đầu xác thực đầu vào mà không có lý do thực sự.
Người gọi phải là người có đủ thông tin về ngữ cảnh để đưa ra những quyết định này, hay nói cách khác, điều tồi tệ sẽ ít đáng sợ hơn khi bạn biết nhiều hơn. Mặt khác, hàm chỉ nên thực hiện lời hứa của người gọi rằng bộ nhớ mà nó được trỏ tới là an toàn để hoạt động như dự kiến. (Tài liệu tham khảo vẫn là địa chỉ bộ nhớ, nhưng về mặt khái niệm đại diện cho một lời hứa hợp lệ.)
std::unique_ptr
đểstd::vector<std::unique_ptr>
tranh cãi?