make_unique và chuyển tiếp hoàn hảo


216

Tại sao không có std::make_uniquemẫu hàm trong thư viện C ++ 11 tiêu chuẩn? Tôi tìm thấy

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

dài dòng một chút. Không phải sau đây sẽ đẹp hơn nhiều?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

Điều này ẩn giấu newđộc đáo và chỉ đề cập đến loại một lần.

Dù sao, đây là nỗ lực của tôi trong việc thực hiện make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Tôi mất khá nhiều thời gian để có được std::forwardcông cụ biên dịch, nhưng tôi không chắc liệu nó có đúng không. Là nó? Chính xác thì std::forward<Args>(args)...có nghĩa là gì? Trình biên dịch làm gì cho điều đó?


1
Khá chắc chắn rằng chúng tôi đã có cuộc thảo luận này trước đây ... cũng lưu ý rằng unique_ptrcần có một tham số mẫu thứ hai mà bạn nên cho phép bằng cách nào đó - khác với shared_ptr.
Kerrek SB

5
@Kerrek: Tôi không nghĩ sẽ hợp lý khi tham số hóa make_uniquevới một deleter tùy chỉnh, bởi vì rõ ràng nó phân bổ thông qua cũ đơn giản newvà do đó phải sử dụng đơn giản cũ delete:)
fredoverflow

1
@Fred: Đó là sự thật. Vì vậy, đề xuất make_uniquesẽ được giới hạn trong newphân bổ ... tốt, thật tốt nếu bạn muốn viết nó, nhưng tôi có thể thấy tại sao một cái gì đó như thế không phải là một phần của tiêu chuẩn.
Kerrek SB

4
Trên thực tế, tôi thích sử dụng một make_uniquekhuôn mẫu vì hàm tạo của hàm std::unique_ptrrõ ràng và do đó, nó là dài dòng để trả về unique_ptrtừ một hàm. Ngoài ra, tôi thích sử dụng auto p = make_unique<foo>(bar, baz)hơn std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.

Câu trả lời:


157

Herb Sutter, chủ tịch ủy ban C ++ chuẩn, viết về mình trên blog :

C ++ 11 không bao gồm make_uniquemột phần là một sự giám sát và gần như chắc chắn nó sẽ được thêm vào trong tương lai.

Ông cũng đưa ra một triển khai giống hệt với triển khai của OP.

Chỉnh sửa: std::make_unique bây giờ là một phần của C ++ 14 .


2
Một make_uniquemẫu chức năng không tự đảm bảo các cuộc gọi an toàn ngoại lệ. Nó dựa vào quy ước, rằng người gọi sử dụng nó. Ngược lại, kiểm tra loại tĩnh nghiêm ngặt (là khác biệt chính giữa C ++ và C) được xây dựng trên ý tưởng thực thi an toàn, thông qua các loại. Và đối với điều đó, make_uniquecó thể chỉ đơn giản là một lớp thay vì chức năng. Ví dụ: xem bài viết trên blog của tôi từ tháng 5 năm 2010. Nó cũng được liên kết đến từ cuộc thảo luận trên blog của Herb.
Chúc mừng và hth. - Alf

1
@ DavidRodríguez-dribeas: đọc blog của Herb để hiểu vấn đề an toàn ngoại lệ. quên đi "không được thiết kế để có nguồn gốc", đó chỉ là rác rưởi. thay vì gợi ý của tôi về việc tạo make_uniquemột lớp, bây giờ tôi nghĩ rằng tốt hơn là biến nó thành một hàm tạo ra một make_unique_t, lý do cho điều đó là một vấn đề với phân tích sai lầm nhất :-).
Chúc mừng và hth. - Alf

1
@ Cheersandhth.-Alf: Có thể chúng tôi đã đọc một bài viết khác, bởi vì bài tôi vừa đọc rõ ràng make_uniquecung cấp đảm bảo ngoại lệ mạnh mẽ. Hoặc có thể bạn đang trộn công cụ với việc sử dụng nó, trong trường hợp đó không có chức năng nào là ngoại lệ an toàn. Xem xét void f( int *, int* ){}, rõ ràng đưa ra sự no throwđảm bảo, nhưng theo lý luận của bạn, nó không phải là ngoại lệ an toàn, vì nó có thể bị lạm dụng. Tồi tệ hơn, void f( int, int ) {}cũng không ngoại lệ an toàn!: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas

2
@ Cheersandhth.-Alf: Tôi hỏi về các vấn đề ngoại lệ trong make_unique thực hiện như trên và bạn chỉ cho tôi một bài viết của Sutter, các bài viết mà tôi google-fu đã chỉ cho tôi đến trạng thái mà make_uniquecung cấp các bảo đảm ngoại lệ mạnh mẽ , điều này mâu thuẫn tuyên bố của bạn ở trên. Nếu bạn có một bài viết khác tôi đang quan tâm đến việc đọc nó. Vì vậy, câu hỏi ban đầu của tôi là thế nào make_unique(như được định nghĩa ở trên) không ngoại lệ an toàn? (Sidenote: vâng, tôi nghĩ rằng điều đó make_unique cải thiện sự an toàn ngoại lệ ở những nơi có thể áp dụng)
David Rodríguez - dribeas

3
... Ngoài ra tôi không theo đuổi một make_uniquechức năng ít an toàn hơn để sử dụng . Đầu tiên tôi không thấy cách này không an toàn và tôi không thấy việc thêm một loại bổ sung sẽ làm cho nó an toàn hơn . Những gì tôi biết là tôi quan tâm đến việc hiểu các vấn đề mà việc triển khai này có thể có - Tôi không thể thấy bất kỳ-- và cách thực hiện thay thế sẽ giải quyết chúng. Là gì ướcmake_uniquephụ thuộc vào? Làm thế nào bạn sẽ sử dụng kiểm tra loại để thực thi an toàn? Đó là hai câu hỏi mà tôi rất thích một câu trả lời.
David Rodríguez - dribeas

78

Đẹp, nhưng Stephan T. Lavavej (được biết đến với tên STL) có một giải pháp tốt hơn make_unique, hoạt động chính xác cho phiên bản mảng.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

Điều này có thể được nhìn thấy trên video Core C ++ 6 của anh ấy .

Phiên bản cập nhật của phiên bản make_unique của STL hiện có sẵn dưới dạng N3656 . Phiên bản này đã được thông qua vào dự thảo C ++ 14.


make_unique được đề xuất bởi Stephen T Lavalej trong bản cập nhật tiêu chuẩn tiếp theo.
mẫu số

Đây là một thích anh ấy nói về việc thêm nó. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/ Kẻ
tominator

Tại sao thực hiện tất cả các chỉnh sửa không cần thiết xeo, nó vẫn ổn như vậy. Mã đó chính xác như Stephen T. Lavalej đã đặt nó và anh ta làm việc cho dinkumware duy trì thư viện tiêu chuẩn. Bạn đã nhận xét về bức tường đó, vì vậy bạn nên biết.
mẫu số

3
Việc thực hiện make_uniquenên đi trong một tiêu đề. Các tiêu đề không nên nhập một không gian tên (xem Mục # 59 trong sách "Tiêu chuẩn mã hóa C ++" của Sutter / Alexandrescu). Thay đổi của Xeo giúp tránh khuyến khích các thực hành xấu.
Bret Kuhns

thật không may, tôi không hỗ trợ bởi VC2010, thậm chí cả VC2012, cả hai đều không hỗ trợ tham số
tempalte varidic

19

std::make_sharedkhông chỉ là tốc ký cho std::shared_ptr<Type> ptr(new Type(...));. Nó làm một cái gì đó mà bạn không thể làm mà không có nó.

Để thực hiện công việc của mình, std::shared_ptrphải phân bổ một khối theo dõi ngoài việc giữ bộ lưu trữ cho con trỏ thực tế. Tuy nhiên, vì std::make_sharedphân bổ đối tượng thực tế, có thể std::make_sharedphân bổ cả đối tượng khối theo dõi trong cùng một khối bộ nhớ.

Vì vậy, trong khi std::shared_ptr<Type> ptr = new Type(...);sẽ là hai cấp phát bộ nhớ (một cho new, một trong std::shared_ptrkhối theo dõi), std::make_shared<Type>(...)sẽ phân bổ một khối bộ nhớ.

Đó là điều quan trọng đối với nhiều người dùng tiềm năng std::shared_ptr. Điều duy nhất std::make_uniquesẽ làm là thuận tiện hơn một chút. Không có gì hơn thế.


2
Nó không bắt buộc. Gợi ý, nhưng không bắt buộc.
Cún con

3
Nó không chỉ để thuận tiện, nó cũng sẽ cải thiện an toàn ngoại lệ trong một số trường hợp nhất định. Xem câu trả lời của Kerrek SB cho điều đó.
Piotr99

19

Mặc dù không có gì ngăn bạn viết người trợ giúp của riêng bạn, tôi tin rằng lý do chính để cung cấp make_shared<T>trong thư viện là nó thực sự tạo ra một loại con trỏ chia sẻ nội bộ khác với shared_ptr<T>(new T), được phân bổ khác nhau và không có cách nào để đạt được điều này nếu không có sự tận tâm người giúp đỡ.

make_uniqueMặt khác, trình bao bọc của bạn chỉ là đường cú pháp xung quanh một newbiểu thức, vì vậy trong khi nó có thể trông vừa mắt, nó không mang bất cứ thứ gì newlên bàn. Sửa lỗi: thực tế điều này không đúng: Có một lệnh gọi hàm để bọc newbiểu thức cung cấp sự an toàn ngoại lệ, ví dụ trong trường hợp bạn gọi một hàm void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Có hai nguyên liệu newkhông có kết quả liên quan đến nhau có nghĩa là nếu một biểu thức mới không thành công với một ngoại lệ, thì biểu thức kia có thể rò rỉ tài nguyên. Về lý do tại sao không có make_uniquetrong tiêu chuẩn: Nó chỉ bị lãng quên. (Điều này thỉnh thoảng xảy ra. Cũng không có std::cbegintiêu chuẩn toàn cầu mặc dù phải có một cái.)

Cũng lưu ý rằng unique_ptrcó một tham số mẫu thứ hai mà bạn nên cho phép bằng cách nào đó; điều này khác với shared_ptr, trong đó sử dụng kiểu xóa để lưu trữ các thông số tùy chỉnh mà không biến chúng thành một phần của kiểu.


1
@FredOverflow: Con trỏ dùng chung là một lớp tương đối phức tạp; bên trong nó giữ một khối điều khiển tham chiếu đa hình, nhưng có một số loại khối điều khiển khác nhau. shared_ptr<T>(new T)sử dụng một trong số họ, make_shared<T>()sử dụng một cái khác Cho phép đó là một điều tốt, và phiên bản chia sẻ theo nghĩa nào đó là con trỏ chia sẻ trọng lượng nhẹ nhất bạn có thể nhận được.
Kerrek SB

15
@FredOverflow: shared_ptrphân bổ một khối bộ nhớ động để theo kịp số lượng và hành động "xử lý" khi bạn tạo một shared_ptr. Nếu bạn vượt qua con trỏ một cách rõ ràng, nó cần tạo một khối "mới", nếu bạn sử dụng make_sharednó có thể bó đối tượng của bạn và dữ liệu vệ tinh trong một khối bộ nhớ (một new) dẫn đến phân bổ / giải quyết nhanh hơn, ít phân mảnh hơn và (thông thường ) hành vi bộ nhớ cache tốt hơn.
Matthieu M.

2
Tôi nghĩ rằng tôi cần xem lại shared_ptr và bạn bè ...
fredoverflow

4
-1 "Mặt khác, trình bao bọc make_unique của bạn chỉ là đường cú pháp xung quanh một biểu thức mới, vì vậy trong khi nó có thể trông vừa mắt, nó không mang lại điều gì mới mẻ cho bảng." sai. Nó mang lại khả năng gọi ngoại lệ chức năng an toàn. Nó không mang lại sự đảm bảo, tuy nhiên; để làm điều đó, nó sẽ cần phải là lớp để các đối số chính thức có thể được khai báo của lớp đó (Herb mô tả đó là sự khác biệt giữa chọn tham gia và từ chối).
Chúc mừng và hth. - Alf

1
@ Cheersandhth.-Alf: Vâng, đó là sự thật. Tôi đã nhận ra điều này. Tôi sẽ chỉnh sửa câu trả lời.
Kerrek SB

13

Trong C ++ 11 ... cũng được sử dụng (trong mã mẫu) cho "mở rộng gói".

Yêu cầu là bạn sử dụng nó như một hậu tố của một biểu thức có chứa một gói tham số chưa được mở rộng và nó sẽ chỉ áp dụng biểu thức cho từng thành phần của gói.

Ví dụ: xây dựng trên ví dụ của bạn:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

Cái sau là không chính xác tôi nghĩ.

Ngoài ra, gói đối số có thể không được chuyển đến một hàm chưa được mở rộng. Tôi không chắc chắn về một gói các tham số mẫu.


1
Rất vui khi thấy điều này đánh vần. Tại sao không bao gồm các tham số mẫu matrixdic, quá?
Kerrek SB

@Kerrek: vì tôi không chắc lắm về cú pháp lạ của nó và tôi không biết có nhiều người đã chơi với nó không. Vì vậy, tôi sẽ giữ những gì tôi biết. Nó có thể đảm bảo một mục Câu hỏi thường gặp về c ++, nếu ai đó có đủ động lực và hiểu biết đủ, cho cú pháp dao động khá hoàn chỉnh.
Matthieu M.

Cú pháp là std::forward<Args>(args)..., mở rộng đến forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB

1
: Tôi nghĩ rằng forwardthực sự luôn luôn yêu cầu một tham số mẫu, phải không?
Kerrek SB

1
@Howard: đúng rồi! Buộc khởi tạo kích hoạt cảnh báo ( ideone.com/GDNHb )
Matthieu M.

5

Lấy cảm hứng từ việc thực hiện bởi Stephan T. Lavavej, tôi nghĩ rằng thật tuyệt khi có một make_unique hỗ trợ các phạm vi mảng, nó trên github và tôi rất thích nhận được ý kiến ​​về nó. Nó cho phép bạn làm điều này:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
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.