Sự khác biệt giữa std :: make_unique và std :: unique_ptr với mới


130

std::make_uniquebất kỳ lợi ích hiệu quả như thế std::make_sharednào?

So với xây dựng thủ công std::unique_ptr:

std::make_unique<int>(1);         // vs
std::unique_ptr<int>(new int(1));

make_sharedbất kỳ hiệu quả hơn so với chỉ viết mã tay dài?
Ed Heal

9
@EdHeal Có thể, vì make_sharedcó thể phân bổ cả không gian cho đối tượng và không gian cho khối điều khiển cùng nhau trong một phân bổ duy nhất. Cái giá phải trả là đối tượng không thể được tách riêng ra khỏi khối điều khiển, vì vậy nếu bạn sử dụng weak_ptrnhiều thì cuối cùng bạn có thể sử dụng nhiều bộ nhớ hơn.
bames53

Có lẽ đây là một điểm khởi đầu tốt stackoverflow.com/questions/9302296/ từ
Ed Heal

Câu trả lời:


140

Động lực đằng sau make_uniquechủ yếu là hai lần:

  • make_uniquelà an toàn để tạo tạm thời, trong khi với việc sử dụng rõ ràng newbạn phải nhớ quy tắc về việc không sử dụng tạm thời không tên.

    foo(make_unique<T>(), make_unique<U>()); // exception safe
    
    foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
  • Việc bổ sung make_uniquecuối cùng có nghĩa là chúng ta có thể bảo mọi người 'không bao giờ' sử dụng newthay vì quy tắc trước đó để "không bao giờ" sử dụng newtrừ khi bạn thực hiện unique_ptr".

Ngoài ra còn có một lý do thứ ba:

  • make_uniquekhông yêu cầu sử dụng loại dự phòng. unique_ptr<T>(new T())->make_unique<T>()

Không có lý do nào liên quan đến việc cải thiện hiệu quả thời gian chạy theo cách sử dụng make_shared(do tránh phân bổ thứ hai, với chi phí sử dụng bộ nhớ cao nhất có khả năng cao hơn).

* Dự kiến ​​C ++ 17 sẽ bao gồm thay đổi quy tắc có nghĩa là điều này không còn không an toàn. Xem các giấy tờ của ủy ban C ++ P0400R0P0145R3 .


Nó sẽ có ý nghĩa hơn để nói std::unique_ptrstd::shared_ptrlà lý do tại sao chúng ta có thể nói với mọi người "không bao giờ sử dụng new."
Timothy Shields

2
@TimothyShields Vâng, đó là điều tôi muốn nói. Chỉ là trong C ++ 11 chúng ta có make_sharedvà đó make_uniquelà phần cuối cùng bị thiếu trước đây.
bames53

1
Bất kỳ cách nào bạn có thể đề cập ngắn gọn, hoặc liên kết đến, lý do không sử dụng tạm thời không tên?
Dan Nissenbaum

14
Trên thực tế, từ stackoverflow.com/a/19472607/368896 , tôi đã có nó ... Từ câu trả lời đó, hãy xem xét các cuộc gọi chức năng sau đây f: f(unique_ptr<T>(new T), function_that_can_throw());- trích dẫn câu trả lời: Trình biên dịch được phép gọi (theo thứ tự): new T, function_that_can_throw(), unique_ptr<T>(...). Rõ ràng nếu function_that_can_throwthực sự ném thì bạn bị rò rỉ. make_uniquengăn chặn trường hợp này Vì vậy, câu hỏi của tôi được trả lời.
Dan Nissenbaum

3
Một lý do tôi đã từng phải sử dụng std :: unique_ptr <T> (new T ()) là vì hàm tạo của T là riêng tư. Ngay cả khi lệnh gọi std :: make_unique là trong phương thức nhà máy công cộng của lớp T, nó vẫn không được biên dịch vì một trong các phương thức cơ bản của std :: make_unique không thể truy cập vào hàm tạo riêng. Tôi không muốn kết bạn với phương thức đó vì tôi không muốn dựa vào việc triển khai std :: make_unique. Vì vậy, giải pháp duy nhất là, gọi mới trong phương thức nhà máy của tôi là lớp T, và sau đó bọc nó trong một std :: unique_ptr <T>.
Patrick

14

std::make_uniquestd::make_sharedcó vì hai lý do:

  1. Vì vậy, bạn không phải liệt kê rõ ràng các đối số kiểu mẫu.
  2. An toàn ngoại lệ bổ sung đối với việc sử dụng std::unique_ptrhoặc std::shared_ptrxây dựng. (Xem phần Ghi chú tại đây .)

Nó không thực sự về hiệu quả thời gian chạy. Có một chút về khối điều khiển và Tđược phân bổ cùng một lúc, nhưng tôi nghĩ đó là phần thưởng nhiều hơn và ít động lực hơn cho các chức năng này tồn tại.


Họ cũng ở đó vì ngoại lệ - an toàn.
0x499602D2

@ 0x499602D2 Và đó, bổ sung tốt. Trang này nói về điều đó.
Timothy Shields

Đối với những người đọc trong tương lai, C ++ 17 không cho phép xen kẽ các đối số chức năng để đối số cho ngoại lệ - an toàn không còn tồn tại nữa. Việc cấp phát bộ nhớ cho hai song song std::make_sharedsẽ đảm bảo rằng ít nhất một trong số chúng được bọc trong con trỏ thông minh trước khi phân bổ bộ nhớ khác xảy ra, do đó không có rò rỉ.
MathBunny

7

Một lý do tại sao bạn sẽ phải sử dụng std::unique_ptr(new A())hoặc std::shared_ptr(new A())trực tiếp thay vì std::make_*()không thể truy cập vào hàm tạo của lớp Angoài phạm vi hiện tại.


0

Xem xét chức năng gọi

void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... }

Giả sử rằng mới A()thành công, nhưng mới B()ném một ngoại lệ: bạn bắt nó để tiếp tục thực hiện chương trình bình thường của bạn. Thật không may, tiêu chuẩn C ++ không yêu cầu đối tượng A bị phá hủy và bộ nhớ của nó bị giải phóng: bộ nhớ âm thầm bị rò rỉ và không có cách nào để dọn sạch nó. Bằng cách gói A và B vào std::make_uniquesbạn, chắc chắn rò rỉ sẽ không xảy ra:

void function(std::make_unique<A>(), std::make_unique<B>()) { ... }

Vấn đề ở đây là std::make_unique<A>std::make_unique<B>bây giờ là các đối tượng tạm thời và việc dọn dẹp các đối tượng tạm thời được chỉ định chính xác trong tiêu chuẩn C ++: các hàm hủy của chúng sẽ được kích hoạt và giải phóng bộ nhớ. Vì vậy, nếu bạn có thể, luôn luôn thích phân bổ các đối tượng bằng cách sử dụng std::make_uniquestd::make_shared.

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.