Sự khác biệt là std::make_sharedthực hiện một phân bổ heap, trong khi gọi hàm std::shared_ptrtạ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_sharedthự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_ptrtạ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 Rhstạ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_ptrtạ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_sharedthay 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_ptrcó 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_ptrs giữ khối điều khiển sống?
Phải có một cách để weak_ptrs 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_ptrs 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_ptrsố đếm và weak_ptrsố đếm đều chạm 0.
Quay lại std::make_shared
Vì std::make_sharedthự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_ptrs hay weak_ptrlà 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 newvà hàm shared_ptrtạ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_ptrcò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_ptrcòn sống.