Tại sao sử dụng std :: make_unique trong C ++ 17?


96

Theo như tôi hiểu, C ++ 14 đã giới thiệu std::make_uniquebởi vì, do thứ tự đánh giá tham số không được chỉ định, điều này không an toàn:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Giải thích: nếu lần đánh giá đầu tiên cấp phát bộ nhớ cho con trỏ thô, sau đó các lệnh gọi g()và một ngoại lệ được đưa ra trước khi std::unique_ptrxây dựng, thì bộ nhớ sẽ bị rò rỉ.)

Gọi điện std::make_uniquelà một cách để hạn chế thứ tự cuộc gọi, do đó giúp mọi thứ an toàn:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Kể từ đó, C ++ 17 đã làm rõ thứ tự đánh giá, làm cho Cú pháp A cũng an toàn, vì vậy đây là câu hỏi của tôi: vẫn còn lý do để sử dụng hàm tạo std::make_uniqueover std::unique_ptr's trong C ++ 17? Bạn có thể cho một số ví dụ?

Hiện tại, lý do duy nhất tôi có thể tưởng tượng là nó chỉ cho phép nhập MyClassmột lần (giả sử bạn không cần dựa vào tính đa hình với std::unique_ptr<Base>(new Derived(param))). Tuy nhiên, đó có vẻ như là một lý do khá yếu, đặc biệt là khi std::make_uniquekhông cho phép chỉ định một trình phân định trong khi phương std::unique_ptrthức khởi tạo của thì không.

Và chỉ cần nói rõ, tôi không ủng hộ việc xóa std::make_uniquekhỏi Thư viện Chuẩn (ít nhất là giữ cho nó có ý nghĩa đối với khả năng tương thích ngược), mà là tự hỏi liệu vẫn còn những tình huống mà nó được ưu tiên hơnstd::unique_ptr


4
Tuy nhiên, đó có vẻ là một lý do khá yếu -> Tại sao lại là một lý do yếu? Nó có hiệu quả làm giảm sự trùng lặp mã của loại. Đối với trình xóa, tần suất bạn sử dụng trình xóa tùy chỉnh khi bạn sử dụng std::unique_ptr? Nó không phải là một lý lẽ để chống lạimake_unique
llllllllll

2
Tôi nói đó là một lý do yếu bởi vì nếu không có std::make_uniquengay từ đầu, tôi không nghĩ đó là lý do đủ để thêm nó vào STL, đặc biệt khi đó là một cú pháp ít biểu đạt hơn so với việc sử dụng hàm tạo, chứ không phải hơn
Eternal

1
Nếu bạn có một chương trình, được tạo bằng c ++ 14, sử dụng make_unique, bạn không muốn hàm bị xóa khỏi stl. Hoặc nếu bạn muốn nó tương thích ngược.
Serge

2
@Serge Đó là một điểm tốt, nhưng nó có một chút ngoài đối tượng của câu hỏi của tôi. Tôi sẽ chỉnh sửa để làm cho nó rõ ràng hơn
Eternal

1
@Eternal vui lòng ngừng tham chiếu đến Thư viện tiêu chuẩn C ++ dưới dạng STL vì nó không chính xác và gây nhầm lẫn. Xem stackoverflow.com/questions/5205491/…
Marandil

Câu trả lời:


74

Bạn nói đúng rằng lý do chính đã bị loại bỏ. Vẫn có những nguyên tắc không sử dụng mới và đó là lý do gõ ít hơn (không phải lặp lại kiểu hoặc sử dụng từ new). Phải thừa nhận rằng đó không phải là những lập luận mạnh mẽ nhưng tôi thực sự không muốn thấy newtrong mã của mình.

Cũng đừng quên về tính nhất quán. Bạn hoàn toàn nên sử dụng make_sharedđể sử dụng make_uniquelà tự nhiên và phù hợp với các mô hình. Sau đó, thật đơn giản nếu thay đổi std::make_unique<MyClass>(param)thành std::make_shared<MyClass>(param)(hoặc ngược lại) trong đó cú pháp A yêu cầu viết lại nhiều hơn.


41
@reggaeguitar Nếu tôi thấy lỗi, newtôi cần dừng lại và nghĩ: con trỏ này sẽ tồn tại được bao lâu? Tôi đã xử lý nó một cách chính xác? Nếu có một ngoại lệ, mọi thứ có được dọn dẹp một cách chính xác? Tôi không muốn tự hỏi mình những câu hỏi đó và lãng phí thời gian vào nó và nếu tôi không sử dụng new, tôi không cần phải hỏi những câu hỏi đó.
NathanOliver 20/1218

5
Hãy tưởng tượng bạn thực hiện một grep trên tất cả các tệp nguồn của dự án của bạn và không tìm thấy một tệp nào new. Điều này sẽ không tuyệt vời phải không?
Sebastian Mach

5
Ưu điểm chính của hướng dẫn "không sử dụng mới" là nó đơn giản, vì vậy đây là một hướng dẫn dễ dàng cung cấp cho các nhà phát triển ít kinh nghiệm mà bạn có thể đang làm việc cùng. Tôi đã không nhận ra lúc đầu, nhưng có giá trị trong và của chính nó
Eternal

@NathanOliver Bạn thực sự không hoàn toàn phải sử dụng std::make_shared- hãy tưởng tượng một trường hợp đối tượng được phân bổ là rộng lớn và có rất nhiều std::weak_ptrtrỏ -s với nó: nó sẽ là tốt hơn để cho các đối tượng bị xóa ngay sau khi chia sẻ cuối cùng con trỏ bị phá hủy và chỉ tồn tại với một khu vực chia sẻ nhỏ.
Dev Null

1
@NathanOliver, bạn sẽ không. Những gì tôi đang nói đến là nhược điểm của std::make_shared stackoverflow.com/a/20895705/8414561 trong đó bộ nhớ được sử dụng để lưu trữ đối tượng không thể được giải phóng cho đến khi std::weak_ptrbiến mất cuối cùng (ngay cả khi tất cả std::shared_ptr-s trỏ đến nó (và do đó bản thân đối tượng) đã bị phá hủy).
Dev Null

50

make_uniquephân biệt Tvới T[]T[N], unique_ptr(new ...)không.

Bạn có thể dễ dàng nhận được hành vi không xác định bằng cách chuyển một con trỏ đã được chỉnh new[]sửa đến a unique_ptr<T>hoặc bằng cách chuyển một con trỏ đã được chỉnh newsửa đến a unique_ptr<T[]>.


Nó tồi tệ hơn: Nó không chỉ không, mà còn không thể.
Deduplicator

22

Lý do là để có mã ngắn hơn mà không bị trùng lặp. Đối chiếu

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Bạn tiết kiệm MyClass, newvà niềng răng. Chi phí chỉ có một nhân vật hơn trong làm so với ptr .


2
Vâng, như tôi đã nói trong câu hỏi, tôi có thể thấy nó ít gõ hơn chỉ với một đề cập đến MyClass, nhưng tôi đã tự hỏi liệu có lý do mạnh mẽ hơn để sử dụng nó không
Eternal

2
Trong nhiều trường hợp, hướng dẫn khấu trừ sẽ giúp loại bỏ <MyClass>phần trong biến thể đầu tiên.
AnT

9
Nó đã được nói trong các nhận xét cho các câu trả lời khác, nhưng trong khi c ++ 17 giới thiệu loại trừ kiểu mẫu cho các hàm tạo, trong trường hợp std::unique_ptrnó không được phép. Nó liên quan đến việc phân biệt std::unique_ptr<T>std::unique_ptr<T[]>
Vĩnh viễn

19

Mọi việc sử dụng newđều phải được kiểm tra cẩn thận để đảm bảo tính đúng đắn suốt đời; nó có bị xóa không? Chỉ một lần?

Mọi việc sử dụng make_uniquekhông dành cho những đặc điểm phụ đó; miễn là đối tượng sở hữu có thời gian tồn tại "đúng", nó đệ quy làm cho con trỏ duy nhất có "đúng".

Bây giờ, đúng unique_ptr<Foo>(new Foo())là giống hệt nhau theo mọi cách từ 1 đến make_unique<Foo>(); nó chỉ yêu cầu đơn giản hơn "gửi mã nguồn của bạn cho tất cả các mục đích sử dụng newđể kiểm tra chúng".


1 thực sự là một lời nói dối trong trường hợp chung. Chuyển tiếp hoàn hảo không phải là hoàn hảo, {}init mặc định, mảng đều là ngoại lệ.


Về mặt kỹ thuật unique_ptr<Foo>(new Foo)không hoàn toàn giống với make_unique<Foo>()... cái sau thì có new Foo()Nhưng ngược lại, có.
Barry

@barry true, có thể sử dụng toán tử quá tải mới.
Yakk - Adam Nevraumont

@dedup đó là phù thủy C ++ 17 xấu xa nào vậy?
Yakk - Adam Nevraumont

2
@Deduplicator trong khi c ++ 17 đã giới thiệu loại trừ mẫu cho các hàm tạo, trong trường hợp std::unique_ptrnó không được phép. Nếu có liên quan đến việc phân biệt std::unique_ptr<T>std::unique_ptr<T[]>
Vĩnh cửu

@ Yakk-AdamNevraumont Ý tôi không phải là quá tải mới, tôi chỉ muốn nói đến default-init so với value-init.
Barry

0

Kể từ đó, C ++ 17 đã làm rõ thứ tự đánh giá, làm cho Cú pháp A cũng an toàn

Điều đó thực sự không đủ tốt. Việc dựa vào một điều khoản kỹ thuật mới được giới thiệu gần đây để đảm bảo an toàn không phải là một thực tiễn mạnh mẽ:

  • Ai đó có thể biên dịch mã này trong C ++ 14.
  • Bạn sẽ khuyến khích việc sử dụng dữ liệu thô newở nơi khác, ví dụ bằng cách sao chép-dán ví dụ của bạn.
  • Như SM gợi ý, vì có sự trùng lặp mã, một loại có thể bị thay đổi mà không thay đổi được loại còn lại.
  • Một số loại tái cấu trúc IDE tự động có thể di chuyển nó đến newnơi khác (được, được, không có nhiều cơ hội như vậy).

Nói chung, đó là một ý tưởng hay để mã của bạn phù hợp / mạnh mẽ / hợp lệ rõ ràng mà không cần dùng đến ngôn ngữ sắp xếp, tra cứu các điều khoản kỹ thuật nhỏ hoặc khó hiểu trong tiêu chuẩn.

(Về cơ bản đây là lập luận tương tự mà tôi đã đưa ra ở đây về thứ tự phá hủy tuple.)


-1

Hãy xem xét hàm void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Giả sử rằng A () mới thành công, nhưng B () mới ném ra một ngoại lệ: bạn bắt nó để tiếp tục thực hiện bình thường chương trình 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ị phân bổ: bộ nhớ âm thầm rò rỉ và không có cách nào để dọn dẹp nó. Bằng cách gói A và B vào std :: make_uniques, bạn chắc chắn rằng việc rò rỉ sẽ không xảy ra:

void function (std :: make_unique (), std :: make_unique ()) {...} Vấn đề ở đây là std :: make_unique và std :: make_unique hiện 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 trình hủy của chúng sẽ được kích hoạt và bộ nhớ được giải phóng. Vì vậy, nếu bạn có thể, hãy luôn thích phân bổ các đối tượng bằng cách sử dụng std :: make_unique và std :: make_shared.


4
Tác giả đã chỉ định rõ ràng trong câu hỏi rằng trong C ++ 17 rò rỉ sẽ không xảy ra nữa. "Kể từ đó, C ++ 17 đã làm rõ thứ tự đánh giá, làm cho Cú pháp A cũng an toàn, vì vậy đây là câu hỏi của tôi: (...)". Bạn đã không trả lời câu hỏi của anh ấy.
R2RT
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.