shared_ptr vào một mảng: nó có nên được sử dụng không?


172

Chỉ cần một truy vấn nhỏ liên quan shared_ptr.

Nó có phải là một thực hành tốt để sử dụng shared_ptrchỉ vào một mảng? Ví dụ,

shared_ptr<int> sp(new int[10]);

Nếu không thì tại sao không? Một lý do tôi đã biết là một lý do không thể tăng / giảm shared_ptr. Do đó, nó không thể được sử dụng như một con trỏ bình thường cho một mảng.


2
FWIT, bạn cũng có thể xem xét chỉ sử dụng std::vector. Bạn phải cẩn thận để vượt qua các mảng xung quanh bằng cách sử dụng các tài liệu tham khảo để bạn không tạo ra các bản sao của nó. Cú pháp để truy cập dữ liệu sạch hơn shared_ptr và thay đổi kích thước nó rất dễ dàng. Và bạn nhận được tất cả sự tốt đẹp của STL nếu bạn muốn.
Nicu Stiurca

6
Nếu kích thước của mảng được xác định tại thời điểm biên dịch, bạn cũng có thể cân nhắc sử dụng std::array. Nó gần giống như một mảng thô, nhưng với ngữ nghĩa phù hợp để sử dụng trong hầu hết các thành phần thư viện. Đặc biệt là các đối tượng loại đó bị phá hủy với delete, không delete[]. Và không giống như vector, nó lưu trữ dữ liệu trực tiếp trong đối tượng, do đó bạn không được phân bổ thêm.
celtschk

Câu trả lời:


268

Với C ++ 17 , shared_ptrcó thể được sử dụng để quản lý một mảng được phân bổ động. Đối shared_ptrsố mẫu trong trường hợp này phải là T[N]hoặc T[]. Vì vậy bạn có thể viết

shared_ptr<int[]> sp(new int[10]);

Từ n4659, [produc.smartptr. Shared.const]

  template<class Y> explicit shared_ptr(Y* p);

Yêu cầu: Y phải là một loại hoàn chỉnh. Biểu thức delete[] p, khi Tlà một kiểu mảng, hoặc delete p, khi Tkhông phải là một kiểu mảng, sẽ có hành vi được xác định rõ và không được ném ngoại lệ.
...
Lưu ý: Khi Tlà một loại mảng, constructor này sẽ không tham gia vào giải quyết tình trạng quá tải nếu biểu thức delete[] plà tốt được hình thành và một trong hai TU[N]Y(*)[N]là mui trần đến T*, hoặc TU[]Y(*)[]là chuyển đổi thành T*. ...

Để hỗ trợ điều này, loại thành viên element_typehiện được định nghĩa là

using element_type = remove_extent_t<T>;

Các phần tử mảng có thể được truy cập bằng cách sử dụng operator[]

  element_type& operator[](ptrdiff_t i) const;

Yêu cầu : get() != 0 && i >= 0 . Nếu TU[N], i < N. ... Lưu ý
: Khi Tkhông phải là kiểu mảng, không xác định được liệu hàm thành viên này có được khai báo hay không. Nếu nó được khai báo, không xác định được kiểu trả về của nó là gì, ngoại trừ việc khai báo (mặc dù không nhất thiết là định nghĩa) của hàm sẽ được định dạng tốt.


Trước khi C ++ 17 , shared_ptrcó thể không được sử dụng để quản lý các mảng cấp phát động. Theo mặc định, shared_ptrsẽ gọi deleteđối tượng được quản lý khi không còn tham chiếu đến nó. Tuy nhiên, khi bạn phân bổ sử dụng, new[]bạn cần gọi delete[], và không delete, để giải phóng tài nguyên.

Để sử dụng chính xác shared_ptrvới một mảng, bạn phải cung cấp một deleter tùy chỉnh.

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

Tạo shared_ptr như sau:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

Bây giờ shared_ptrsẽ gọi chính xác delete[]khi phá hủy đối tượng được quản lý.

Các deleter tùy chỉnh ở trên có thể được thay thế bởi

  • các std::default_deleteđặc tả từng phần với nhiều loại mảng

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • một biểu hiện lambda

    std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

Ngoài ra, trừ khi bạn thực sự cần chia sẻ quyền sở hữu của đối tượng được quản lý, unique_ptrthì phù hợp hơn cho nhiệm vụ này, vì nó có chuyên môn hóa một phần cho các kiểu mảng.

std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]

Những thay đổi được giới thiệu bởi Phần mở rộng C ++ cho Nguyên tắc cơ bản của Thư viện

Một lựa chọn thay thế trước C ++ 17 khác cho các tính năng được liệt kê ở trên được cung cấp bởi Thông số kỹ thuật cơ bản của Thư viện , được tăng cường shared_ptrđể cho phép nó hoạt động tốt trong các trường hợp khi nó sở hữu một mảng các đối tượng. Dự thảo hiện tại về những shared_ptrthay đổi dự kiến ​​cho TS này có thể được tìm thấy trong N4082 . Những thay đổi này sẽ có thể truy cập thông qua std::experimentalkhông gian tên và được bao gồm trong <experimental/memory>tiêu đề. Một vài thay đổi có liên quan để hỗ trợ shared_ptrcho mảng là:

- Định nghĩa của loại thành viên element_typethay đổi

typedef T phần tử_type;

 typedef typename remove_extent<T>::type element_type;

- Thành viên operator[]đang được thêm

 element_type& operator[](ptrdiff_t i) const noexcept;

- Không giống như unique_ptrchuyên môn hóa một phần cho mảng, cả hai shared_ptr<T[]>shared_ptr<T[N]>sẽ hợp lệ và cả hai sẽ dẫn đến delete[]việc được gọi trên mảng đối tượng được quản lý.

 template<class Y> explicit shared_ptr(Y* p);

Yêu cầu : Yphải là một loại hoàn chỉnh. Biểu thức delete[] p, khi Tlà một kiểu mảng, hoặc delete p, khi Tkhông phải là một kiểu mảng, sẽ được định dạng tốt, sẽ có hành vi được xác định rõ và không được ném ngoại lệ. Khi TU[N], Y(*)[N]phải chuyển đổi thành T*; khi TU[], Y(*)[]phải chuyển đổi thành T*; nếu không, Y*sẽ được chuyển đổi thành T*.


9
+1, nhận xét: Ngoài ra còn có Boost shared-array.
jogojapan

5
@ tshah06 shared_ptr::gettrả về một con trỏ tới đối tượng được quản lý. Vì vậy, bạn có thể sử dụng nó nhưsp.get()[0] = 1; ... sp.get()[9] = 10;
Praetorian

55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );xem thêm en.cppreference.com/w/cpp/memory/default_delete
yohjp

2
@Jeremy Nếu kích thước được biết đến vào thời gian biên dịch thì không cần phải viết một lớp cho điều đó, std::shared_ptr<std::array<int,N>>là đủ.
Praetorian

13
Tại sao có unique_ptrđược chuyên môn hóa một phần nhưng shared_ptrkhông?
Adam

28

Một sự thay thế có thể dễ dàng hơn mà bạn có thể sử dụng là shared_ptr<vector<int>>.


5
Vâng, đúng vậy. Hoặc một vectơ là một siêu khối của một mảng - nó có cùng biểu diễn trong bộ nhớ (cộng với siêu dữ liệu) nhưng có thể thay đổi kích thước. Thực sự không có bất kỳ tình huống nào mà bạn muốn một mảng nhưng không thể sử dụng một vectơ.
Timmmm

2
Sự khác biệt, ở đây, là kích thước vectơ còn tĩnh và việc truy cập dữ liệu sẽ được thực hiện với một chỉ định kép. Nếu hiệu suất không phải là vấn đề quan trọng, thì điều này hoạt động, nếu không thì việc chia sẻ một mảng có thể có lý do riêng.
Emilio Garavaglia

4
Sau đó, bạn có thể có thể sử dụng shared_ptr<array<int, 6>>.
Timmmm

10
Sự khác biệt khác là nó hơi lớn hơn và chậm hơn một mảng thô. Nói chung không thực sự là một vấn đề nhưng chúng ta đừng giả vờ rằng 1 == 1.1.
Andrew

2
Có những tình huống mà nguồn dữ liệu trong mảng có nghĩa là không dễ dàng hoặc không cần thiết để chuyển đổi thành một vectơ; chẳng hạn như khi lấy khung hình từ máy ảnh. (Hoặc, đó là sự hiểu biết của tôi, dù sao đi nữa)
Narfanator
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.