Con trỏ thông minh: ai sở hữu đối tượng? [đóng cửa]


114

C ++ là tất cả về quyền sở hữu bộ nhớ - hay còn gọi là ngữ nghĩa quyền sở hữu .

Chủ sở hữu của một phần bộ nhớ được cấp phát động có trách nhiệm giải phóng bộ nhớ đó. Vì vậy, câu hỏi thực sự trở thành ai sở hữu bộ nhớ.

Trong C ++, quyền sở hữu được ghi lại bằng kiểu một con trỏ thô được bao bọc bên trong, do đó trong một chương trình C ++ tốt (IMO), rất hiếm ( hiếm , không phải không bao giờ ) thấy các con trỏ thô được chuyển xung quanh (vì con trỏ thô không có quyền sở hữu suy ra nên chúng ta có thể không cho biết ai sở hữu bộ nhớ và do đó nếu không đọc kỹ tài liệu, bạn không thể biết ai chịu trách nhiệm về quyền sở hữu).

Ngược lại, hiếm khi thấy các con trỏ thô được lưu trữ trong một lớp, mỗi con trỏ thô được lưu trữ trong trình bao bọc con trỏ thông minh của chính nó. ( NB: Nếu bạn không sở hữu một đối tượng, bạn không nên lưu trữ nó vì bạn không thể biết khi nào nó sẽ vượt ra khỏi phạm vi và bị phá hủy.)

Vì vậy, câu hỏi:

  • Mọi người đã xem qua loại ngữ nghĩa sở hữu nào?
  • Những lớp tiêu chuẩn nào được sử dụng để triển khai các ngữ nghĩa đó?
  • Bạn thấy chúng hữu ích trong những tình huống nào?

Cho phép giữ 1 loại quyền sở hữu ngữ nghĩa cho mỗi câu trả lời để chúng có thể được bình chọn lên và xuống riêng lẻ.

Tóm lược:

Về mặt khái niệm, con trỏ thông minh rất đơn giản và việc triển khai đơn giản cũng dễ dàng. Tôi đã thấy nhiều cố gắng triển khai, nhưng luôn luôn bị hỏng theo một cách nào đó mà không rõ ràng đối với các ví dụ và sử dụng thông thường. Vì vậy, tôi khuyên bạn nên luôn sử dụng các con trỏ thông minh đã được kiểm nghiệm tốt từ thư viện hơn là sử dụng con trỏ của riêng bạn. std::auto_ptrhoặc một trong những con trỏ thông minh Boost dường như đáp ứng mọi nhu cầu của tôi.

std::auto_ptr<T>:

Người độc thân sở hữu đối tượng. Được phép chuyển quyền sở hữu.

Cách sử dụng: Điều này cho phép bạn xác định các giao diện thể hiện sự chuyển giao quyền sở hữu rõ ràng.

boost::scoped_ptr<T>

Người độc thân sở hữu đối tượng. KHÔNG được phép chuyển quyền sở hữu.

Cách sử dụng: Được sử dụng để thể hiện quyền sở hữu rõ ràng. Đối tượng sẽ bị phá hủy bởi hàm hủy hoặc khi đặt lại rõ ràng.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Nhiều quyền sở hữu. Đây là một con trỏ đếm tham chiếu đơn giản. Khi số lượng tham chiếu bằng không, đối tượng bị phá hủy.

Cách sử dụng: Khi một đối tượng có thể có nhiều chủ sở hữu với thời gian tồn tại mà không thể xác định tại thời điểm biên dịch.

boost::weak_ptr<T>:

Được sử dụng shared_ptr<T>trong các tình huống có thể xảy ra một chu kỳ con trỏ.

Cách sử dụng: Được sử dụng để dừng các chu kỳ giữ lại các đối tượng khi chỉ có chu kỳ đang duy trì một số tiền được chia sẻ.


14
?? Câu hỏi là gì?
Pacerier

9
Tôi chỉ muốn chỉ ra rằng kể từ khi câu hỏi này được đăng auto_ptr đã không được chấp nhận thay cho unique_ptr (hiện đã được chuẩn hóa)
Juan Campa

In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Điều này có thể được diễn đạt lại không? Tôi không hiểu nó chút nào.
lolololol ol

@lololololol Bạn cắt câu làm đôi. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. Con trỏ RAW không có ngữ nghĩa quyền sở hữu. Nếu bạn không biết chủ sở hữu, bạn cũng không biết ai chịu trách nhiệm xóa đối tượng. Có một số lớp tiêu chuẩn được sử dụng để bọc con trỏ (std :: shared_ptr, std :: unique_ptr, v.v.) xác định quyền sở hữu và do đó xác định ai chịu trách nhiệm xóa con trỏ.
Martin York

1
Trong C ++ 11 + KHÔNG SỬ DỤNG auto_ptr! Sử dụng unique_ptr để thay thế!
val nói Hãy phục hồi Monica vào

Câu trả lời:


20

Đối với tôi, 3 loại này đáp ứng hầu hết các nhu cầu của tôi:

shared_ptr - tham chiếu đếm, phân bổ giao dịch khi bộ đếm đạt đến 0

weak_ptr- tương tự như trên, nhưng nó là 'nô lệ' cho a shared_ptr, không thể phân bổ

auto_ptr- khi việc tạo và phân bổ giao dịch xảy ra bên trong cùng một hàm, hoặc khi đối tượng phải được coi là chỉ một chủ sở hữu. Khi bạn gán một con trỏ này cho một con trỏ khác, con trỏ thứ hai sẽ 'đánh cắp' đối tượng từ con trỏ đầu tiên.

Tôi có triển khai của riêng mình cho những thứ này, nhưng chúng cũng có sẵn trong Boost.

Tôi vẫn truyền các đối tượng bằng tham chiếu ( constbất cứ khi nào có thể), trong trường hợp này, phương thức được gọi phải giả sử đối tượng chỉ còn sống trong thời gian gọi.

Có một loại con trỏ khác mà tôi sử dụng mà tôi gọi là hub_ptr . Đó là khi bạn có một đối tượng phải có thể truy cập được từ các đối tượng được lồng trong nó (thường là một lớp cơ sở ảo). Điều này có thể được giải quyết bằng cách chuyển một weak_ptrcho họ, nhưng nó không có shared_ptrcho chính nó. Vì nó biết những đối tượng này sẽ không tồn tại lâu hơn anh ta, nó chuyển một hub_ptr cho chúng (nó chỉ là một trình bao bọc mẫu cho một con trỏ thông thường).


2
Thay vì tạo lớp con trỏ của riêng bạn (hub_ptr), tại sao bạn không chuyển * cái này cho các đối tượng này và để chúng lưu trữ dưới dạng tham chiếu? Vì bạn thậm chí thừa nhận rằng các đối tượng sẽ bị phá hủy cùng lúc với lớp sở hữu, tôi không hiểu điểm của việc nhảy qua nhiều vòng như vậy.
Michel

4
Về cơ bản nó là một hợp đồng thiết kế để làm cho mọi thứ rõ ràng. Khi đối tượng con nhận được hub_ptr, nó biết rằng đối tượng trỏ sẽ không bị phá hủy trong suốt thời gian tồn tại của đối tượng con và không có quyền sở hữu đối với nó. Cả đối tượng chứa và đối tượng chứa đều đồng ý với một bộ quy tắc rõ ràng. Nếu bạn sử dụng con trỏ rỗng, các quy tắc có thể được ghi lại, nhưng sẽ không được trình biên dịch và mã thực thi.
Fabio Ceconello

1
Cũng lưu ý rằng bạn có thể có #ifdefs để làm cho hub_ptr được nhập vào một con trỏ trần trong các bản dựng phát hành, vì vậy chi phí sẽ chỉ tồn tại trong bản dựng gỡ lỗi.
Fabio Ceconello

3
Lưu ý rằng tài liệu Boost mâu thuẫn với mô tả của bạn về scoped_ptr. Nó nói rằng nó là noncopyablevà rằng quyền sở hữu không thể được chuyển giao.
Alec Thomas,

3
@Alec Thomas, Bạn nói đúng. Tôi đã nghĩ về auto_ptr và đã viết scoped_ptr. Đã sửa.
Fabio Ceconello

23

Mô hình C ++ đơn giản

Trong hầu hết các mô-đun tôi thấy, theo mặc định, người ta cho rằng việc nhận con trỏ không nhận được quyền sở hữu. Trên thực tế, các hàm / phương thức từ bỏ quyền sở hữu con trỏ đều rất hiếm và được thể hiện rõ ràng thực tế đó trong tài liệu của họ.

Mô hình này giả định rằng người dùng chỉ là chủ sở hữu của những gì họ phân bổ rõ ràng . Mọi thứ khác sẽ tự động được xử lý (tại lối ra phạm vi hoặc thông qua RAII). Đây là một mô hình giống C, được mở rộng bởi thực tế là hầu hết các con trỏ thuộc sở hữu của các đối tượng sẽ tự động phân bổ chúng hoặc khi cần thiết (chủ yếu là khi các đối tượng bị phá hủy), và thời gian tồn tại của các đối tượng là có thể dự đoán được (RAII là bạn của bạn, lần nữa).

Trong mô hình này, các con trỏ thô được lưu hành tự do và hầu như không nguy hiểm (nhưng nếu nhà phát triển đủ thông minh, họ sẽ sử dụng các tham chiếu thay thế bất cứ khi nào có thể).

  • con trỏ thô
  • std :: auto_ptr
  • boost :: scoped_ptr

Mô hình C ++ đầu nhọn thông minh

Trong một đoạn mã chứa đầy các con trỏ thông minh, người dùng có thể hy vọng bỏ qua thời gian tồn tại của các đối tượng. Chủ sở hữu không bao giờ là mã người dùng: Bản thân nó là con trỏ thông minh (RAII, một lần nữa). Vấn đề là các tham chiếu vòng tròn trộn lẫn với các con trỏ thông minh được đếm tham chiếu có thể gây nguy hiểm , vì vậy bạn phải đối phó với cả con trỏ dùng chung và con trỏ yếu. Vì vậy, bạn vẫn có quyền sở hữu để xem xét (con trỏ yếu có thể chỉ ra không có gì, ngay cả khi lợi thế của nó so với con trỏ thô là nó có thể cho bạn biết như vậy).

  • boost :: shared_ptr
  • tăng :: yếu_ptr

Phần kết luận

Bất kể mô hình mà tôi mô tả, trừ khi ngoại lệ, việc nhận một con trỏ không phải là nhận quyền sở hữu của nóđiều rất quan trọng là phải biết ai sở hữu ai . Ngay cả đối với mã C ++ sử dụng nhiều tham chiếu và / hoặc con trỏ thông minh.


10

Không có quyền sở hữu chung. Nếu bạn làm vậy, hãy đảm bảo rằng nó chỉ với mã mà bạn không kiểm soát.

Điều đó giải quyết 100% các vấn đề, vì nó buộc bạn phải hiểu cách mọi thứ tương tác.


2
  • Quyền sở hữu chung
  • boost :: shared_ptr

Khi một tài nguyên được chia sẻ giữa nhiều đối tượng. Boost shared_ptr sử dụng phương pháp đếm tham chiếu để đảm bảo tài nguyên được khử phân bổ khi mọi người được vây bắt.


2

std::tr1::shared_ptr<Blah> thường là đặt cược tốt nhất của bạn.


2
shared_ptr là phổ biến nhất. Nhưng còn nhiều nữa. Mỗi loại có cách sử dụng riêng và những chỗ tốt và xấu để kiện. Một chút mô tả sẽ tốt hơn.
Martin York

Nếu bạn gặp khó khăn với trình biên dịch cũ hơn, boost :: shared_ptr <blah> là thứ dựa trên std :: tr1 :: shared_ptr <blah>. Đó là một lớp đủ đơn giản để bạn có thể tách nó khỏi Boost và sử dụng nó ngay cả khi trình biên dịch của bạn không được phiên bản Boost mới nhất hỗ trợ.
Branan 18-08

2

Từ boost, cũng thư viện vùng chứa con trỏ . Chúng hiệu quả hơn và dễ sử dụng hơn một chút so với một vùng chứa con trỏ thông minh tiêu chuẩn, nếu bạn chỉ sử dụng các đối tượng trong ngữ cảnh của vùng chứa của chúng.

Trên Windows, có các con trỏ COM (IUnknown, IDispatch và friends) và các con trỏ thông minh khác nhau để xử lý chúng (ví dụ: CComPtr của ATL và các con trỏ thông minh được tạo tự động bởi câu lệnh "import" trong Visual Studio dựa trên lớp _com_ptr ).


1
  • Một chủ sở hữu
  • boost :: scoped_ptr

Khi bạn cần cấp phát bộ nhớ động nhưng muốn chắc chắn rằng nó được phân bổ trên mọi điểm thoát của khối.

Tôi thấy điều này hữu ích vì nó có thể dễ dàng được gắn lại và phát hành mà không bao giờ phải lo lắng về việc rò rỉ


1

Tôi không nghĩ mình đã từng có quyền sở hữu chung trong thiết kế của mình. Trên thực tế, từ đỉnh đầu của tôi, trường hợp hợp lệ duy nhất mà tôi có thể nghĩ đến là mẫu Flyweight.


1

yasper :: ptr là một phần mềm thay thế nhẹ, boost :: shared_ptr như. Nó hoạt động tốt trong dự án nhỏ của tôi (hiện tại).

Trong trang web tại http://yasper.sourceforge.net/, nó được mô tả như sau:

Tại sao phải viết một con trỏ thông minh C ++ khác? Đã tồn tại một số triển khai con trỏ thông minh chất lượng cao cho C ++, nổi bật nhất là pantheon con trỏ Boost và SmartPtr của Loki. Để có sự so sánh tốt về việc triển khai con trỏ thông minh và khi nào việc sử dụng chúng phù hợp, vui lòng đọc The New C ++: Smart (er) Pointers của Herb Sutter. Trái ngược với các tính năng mở rộng của các thư viện khác, Yasper là một con trỏ đếm tham chiếu tập trung hẹp. Nó tương ứng chặt chẽ với chính sách shared_ptr của Boost và RefCounted / AllowConversion của Loki. Yasper cho phép các lập trình viên C ++ quên đi việc quản lý bộ nhớ mà không cần giới thiệu các phụ thuộc lớn của Boost hoặc phải tìm hiểu về các mẫu chính sách phức tạp của Loki. Triết học

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

Điểm cuối cùng có thể nguy hiểm, vì yasper cho phép các hành động rủi ro (nhưng hữu ích) (chẳng hạn như gán cho con trỏ thô và giải phóng thủ công) mà các triển khai khác không cho phép. Hãy cẩn thận, chỉ sử dụng những tính năng đó nếu bạn biết mình đang làm gì!


1

Có một hình thức chủ sở hữu có thể chuyển nhượng duy nhất được sử dụng thường xuyên khác và nó được ưu tiên sử dụng auto_ptrvì nó tránh được các vấn đề gây ra bởi auto_ptrsự tham nhũng điên cuồng của ngữ nghĩa chuyển nhượng.

Tôi không nói về ai khác hơn swap. Bất kỳ loại nào có swapchức năng phù hợp đều có thể được coi là một tham chiếu thông minh đến một số nội dung mà nó sở hữu cho đến khi quyền sở hữu được chuyển sang một phiên bản khác cùng loại, bằng cách hoán đổi chúng. Mỗi phiên bản vẫn giữ nguyên bản sắc của nó nhưng bị ràng buộc với nội dung mới. Nó giống như một tài liệu tham khảo có thể đóng lại một cách an toàn.

(Đó là một tham chiếu thông minh hơn là một con trỏ thông minh vì bạn không cần phải tham khảo rõ ràng nó để xem nội dung.)

Điều này có nghĩa là auto_ptr trở nên ít cần thiết hơn - nó chỉ cần thiết để lấp đầy những khoảng trống mà các kiểu không có swapchức năng tốt . Nhưng tất cả các container std đều có.


Có thể nó trở nên ít cần thiết hơn (tôi muốn nói rằng scoped_ptr làm cho nó ít cần thiết hơn thế này), nhưng nó sẽ không biến mất. Có chức năng hoán đổi không giúp ích gì cho bạn nếu bạn phân bổ thứ gì đó trên heap và ai đó ném trước khi bạn xóa nó, hoặc đơn giản là bạn quên.
Michel

Đó chính xác là những gì tôi đã nói trong đoạn trước.
Daniel Earwicker

0
  • Một chủ sở hữu: Aka xóa trên Bản sao
  • std :: auto_ptr

Khi người tạo đối tượng muốn chuyển giao quyền sở hữu một cách rõ ràng cho người khác. Đây cũng là một cách ghi lại mã mà tôi đang cung cấp cho bạn và tôi không còn theo dõi nó nữa, vì vậy hãy đảm bảo bạn xóa nó khi hoàn tất.

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.