Sự khác biệt là std::make_shared
thực hiện một phân bổ heap, trong khi gọi hàm std::shared_ptr
tạo thực hiện hai.
Trường hợp phân bổ heap xảy ra ở đâu?
std::shared_ptr
quản lý hai thực thể:
- khối điều khiển (lưu trữ dữ liệu meta, chẳng hạn như số lần đếm, số lần xóa loại, v.v.)
- đối tượng được quản lý
std::make_shared
thực hiện một kế toán phân bổ heap duy nhất cho không gian cần thiết cho cả khối điều khiển và dữ liệu. Trong trường hợp khác, new Obj("foo")
gọi một phân bổ heap cho dữ liệu được quản lý và hàm std::shared_ptr
tạo thực hiện một phân bổ khác cho khối điều khiển.
Để biết thêm thông tin, hãy kiểm tra các ghi chú thực hiện tại cppreference .
Cập nhật I: Ngoại lệ-An toàn
LƯU Ý (2019/08/30) : Đây không phải là vấn đề kể từ C ++ 17, do những thay đổi trong thứ tự đánh giá của các đối số hàm. Cụ thể, mỗi đối số cho một hàm được yêu cầu thực hiện đầy đủ trước khi đánh giá các đối số khác.
Vì OP dường như đang tự hỏi về khía cạnh an toàn ngoại lệ của mọi thứ, tôi đã cập nhật câu trả lời của mình.
Hãy xem xét ví dụ này,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
Bởi vì C ++ cho phép thứ tự đánh giá các biểu thức con tùy ý, nên một thứ tự có thể là:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Bây giờ, giả sử chúng ta nhận được một ngoại lệ được ném ở bước 2 (ví dụ: ngoài ngoại lệ bộ nhớ, hàm Rhs
tạo đã ném một số ngoại lệ). Sau đó chúng tôi mất bộ nhớ được phân bổ ở bước 1, vì sẽ không có cơ hội để dọn sạch nó. Cốt lõi của vấn đề ở đây là con trỏ thô không được chuyển đến hàm std::shared_ptr
tạo ngay lập tức.
Một cách để khắc phục điều này là thực hiện chúng trên các dòng riêng biệt để việc đặt hàng độc lập này không thể xảy ra.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
Cách ưa thích để giải quyết điều này tất nhiên là sử dụng std::make_shared
thay thế.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Cập nhật II: Nhược điểm của std::make_shared
Trích dẫn ý kiến của Casey :
Vì chỉ có một phân bổ, bộ nhớ của con trỏ không thể bị phân bổ cho đến khi khối điều khiển không còn được sử dụng. A weak_ptr
có thể giữ khối điều khiển sống vô thời hạn.
Tại sao các trường hợp của weak_ptr
s giữ khối điều khiển sống?
Phải có một cách để weak_ptr
s xác định xem đối tượng được quản lý có còn hợp lệ không (ví dụ: for lock
). Họ làm điều này bằng cách kiểm tra số shared_ptr
s sở hữu đối tượng được quản lý, được lưu trữ trong khối điều khiển. Kết quả là các khối điều khiển còn sống cho đến khi cả shared_ptr
số đếm và weak_ptr
số đếm đều chạm 0.
Quay lại std::make_shared
Vì std::make_shared
thực hiện phân bổ heap duy nhất cho cả khối điều khiển và đối tượng được quản lý, không có cách nào giải phóng bộ nhớ cho khối điều khiển và đối tượng được quản lý một cách độc lập. Chúng ta phải đợi cho đến khi chúng tôi có thể giải phóng cả hai khối điều khiển và đối tượng quản lý, mà sẽ xảy ra là cho đến khi không có shared_ptr
s hay weak_ptr
là còn sống.
Giả sử thay vào đó chúng tôi đã thực hiện hai phân bổ heap cho khối điều khiển và đối tượng được quản lý thông qua new
và hàm shared_ptr
tạo. Sau đó, chúng tôi giải phóng bộ nhớ cho đối tượng được quản lý (có thể sớm hơn) khi không shared_ptr
còn sống và giải phóng bộ nhớ cho khối điều khiển (có thể sau này) khi không weak_ptr
còn sống.