C ++: Con trỏ thông minh, Con trỏ thô, Không con trỏ? [đóng cửa]


48

Trong phạm vi phát triển trò chơi trong C ++, các mẫu ưa thích của bạn liên quan đến việc sử dụng con trỏ là gì (có thể là không, thô, có phạm vi, được chia sẻ hoặc nói cách khác là giữa thông minh và ngu ngốc)?

Bạn có thể cân nhắc

  • quyền sở hữu đối tượng
  • dễ sử dụng
  • chính sách sao chép
  • trên không
  • tài liệu tham khảo theo chu kỳ
  • nền tảng mục tiêu
  • sử dụng với container

Câu trả lời:


32

Sau khi thử nhiều cách tiếp cận khác nhau, hôm nay tôi thấy mình phù hợp với Hướng dẫn về Phong cách Google C ++ :

Nếu bạn thực sự cần ngữ nghĩa con trỏ, scoped_ptr là tuyệt vời. Bạn chỉ nên sử dụng std :: tr1 :: shared_ptr trong các điều kiện rất cụ thể, chẳng hạn như khi các đối tượng cần được giữ bởi các thùng chứa STL. Bạn không bao giờ nên sử dụng auto_ptr. [...]

Nói chung, chúng tôi muốn chúng tôi thiết kế mã với quyền sở hữu đối tượng rõ ràng. Quyền sở hữu đối tượng rõ ràng nhất có được bằng cách sử dụng một đối tượng trực tiếp như một trường hoặc biến cục bộ, mà không sử dụng con trỏ nào cả. [..]

Mặc dù chúng không được khuyến nghị, con trỏ đếm tham chiếu đôi khi là cách đơn giản và thanh lịch nhất để giải quyết vấn đề.


14
Hôm nay, bạn có thể muốn sử dụng std :: unique_ptr thay vì scoped_ptr.
Klaim

24

Tôi cũng đi theo đoàn tàu tư duy "sở hữu mạnh mẽ". Tôi muốn phân định rõ ràng rằng "lớp này sở hữu thành viên này" khi thích hợp.

Tôi hiếm khi sử dụng shared_ptr. Nếu tôi làm, tôi sử dụng tự do weak_ptrbất cứ khi nào tôi có thể để tôi có thể coi nó như một tay cầm đối tượng thay vì tăng số lượng tham chiếu.

Tôi sử dụng scoped_ptrkhắp nơi. Nó cho thấy quyền sở hữu rõ ràng. Lý do duy nhất tôi không tạo ra các đối tượng như một thành viên là vì bạn có thể chuyển tiếp khai báo chúng nếu chúng ở trong phạm vi scoped_ptr.

Nếu tôi cần một danh sách các đối tượng, tôi sử dụng ptr_vector. Nó hiệu quả hơn và có ít tác dụng phụ hơn so với sử dụng vector<shared_ptr>. Tôi nghĩ rằng bạn có thể không thể chuyển tiếp khai báo loại trong ptr_vector (đã được một thời gian), nhưng theo ngữ nghĩa của nó làm cho nó có giá trị theo ý kiến ​​của tôi. Về cơ bản nếu bạn xóa một đối tượng khỏi danh sách thì nó sẽ tự động bị xóa. Điều này cũng cho thấy quyền sở hữu rõ ràng.

Nếu tôi cần tham chiếu đến một cái gì đó, tôi cố gắng làm cho nó trở thành một tham chiếu thay vì một con trỏ trần. Đôi khi điều này không thực tế (tức là bất cứ lúc nào bạn cần một tài liệu tham khảo sau khi đối tượng được xây dựng). Dù bằng cách nào, các tài liệu tham khảo cho thấy rõ ràng rằng bạn không sở hữu đối tượng và nếu bạn đang theo dõi ngữ nghĩa con trỏ được chia sẻ ở mọi nơi khác thì con trỏ trần thường không gây ra bất kỳ sự nhầm lẫn nào (đặc biệt là nếu bạn tuân theo quy tắc "không xóa thủ công") .

Với phương pháp này, một trò chơi iPhone mà tôi đã làm việc chỉ có thể có một deletecuộc gọi duy nhất và đó là trong cây cầu Obj-C đến C ++ mà tôi đã viết.

Nói chung, tôi cho rằng việc quản lý bộ nhớ quá quan trọng đối với con người. Nếu bạn có thể tự động xóa, bạn nên. Nếu chi phí từ shared_ptr quá đắt trong thời gian chạy (giả sử bạn đã tắt hỗ trợ luồng, v.v.), có lẽ bạn nên sử dụng một cái gì đó khác (ví dụ như mẫu xô) để giảm phân bổ động của bạn.


1
Tóm tắt tuyệt vời. Bạn có thực sự có nghĩa là shared_ptr trái ngược với việc bạn đề cập đến smart_ptr không?
jmp97

Vâng, tôi có nghĩa là shared_ptr. Tôi sẽ sửa nó.
Tetrad

10

Sử dụng các công cụ thích hợp cho công việc.

Nếu chương trình của bạn có thể đưa ra các ngoại lệ, đảm bảo mã của bạn là ngoại lệ. Sử dụng con trỏ thông minh, RAII và tránh xây dựng 2 pha là điểm khởi đầu tốt.

Nếu bạn có các tài liệu tham khảo theo chu kỳ không có ngữ nghĩa sở hữu rõ ràng, bạn có thể xem xét sử dụng thư viện bộ sưu tập rác hoặc tái cấu trúc thiết kế của bạn.

Các thư viện tốt sẽ cho phép bạn mã hóa khái niệm không phải là loại vì vậy nó không quan trọng trong hầu hết các trường hợp bạn đang sử dụng loại con trỏ nào ngoài các vấn đề quản lý tài nguyên.

Nếu bạn đang làm việc trong môi trường đa luồng, hãy đảm bảo bạn hiểu nếu đối tượng của bạn có khả năng được chia sẻ qua các luồng. Một trong những lý do chính để xem xét sử dụng boost :: shared_ptr hoặc std :: tr1 :: shared_ptr là vì nó sử dụng số tham chiếu an toàn cho chuỗi.

Nếu bạn lo lắng về việc phân bổ số lượng tham chiếu riêng biệt, có nhiều cách xung quanh vấn đề này. Sử dụng thư viện boost :: shared_ptr, bạn có thể phân bổ các bộ đếm tham chiếu hoặc sử dụng boost :: make_ Shared (tùy chọn của tôi) để phân bổ đối tượng và số tham chiếu trong một phân bổ duy nhất do đó làm giảm hầu hết các lỗi bộ nhớ cache mà mọi người có. Bạn có thể tránh được hiệu suất của việc cập nhật số tham chiếu trong mã quan trọng về hiệu suất bằng cách giữ một tham chiếu đến đối tượng ở mức cao nhất và chuyển xung quanh các tham chiếu trực tiếp đến đối tượng.

Nếu bạn cần quyền sở hữu chung nhưng không muốn trả chi phí đếm tham chiếu hoặc thu gom rác, hãy cân nhắc sử dụng các đối tượng không thay đổi hoặc bản sao trên thành ngữ ghi.

Hãy nhớ rằng chiến thắng hiệu suất lớn nhất của bạn sẽ ở mức kiến ​​trúc, tiếp theo là cấp độ thuật toán và trong khi những lo ngại ở mức độ thấp này rất quan trọng, chúng chỉ nên được giải quyết sau khi bạn giải quyết các vấn đề chính. Nếu bạn đang xử lý các vấn đề về hiệu năng ở mức bộ nhớ cache thì bạn có cả đống vấn đề mà bạn cũng phải lưu ý như chia sẻ sai không liên quan gì đến con trỏ mỗi lần nói.

Nếu bạn đang sử dụng con trỏ thông minh chỉ để chia sẻ các tài nguyên như kết cấu hoặc mô hình, hãy xem xét một thư viện chuyên biệt hơn như Boost.Fly weight.

Khi tiêu chuẩn mới được áp dụng ngữ nghĩa di chuyển, tham chiếu giá trị và chuyển tiếp hoàn hảo sẽ giúp làm việc với các đối tượng và container đắt tiền dễ dàng và hiệu quả hơn nhiều. Cho đến lúc đó, đừng lưu trữ các con trỏ với ngữ nghĩa sao chép phá hủy, chẳng hạn như auto_ptr hoặc unique_ptr, trong Container (khái niệm tiêu chuẩn). Xem xét sử dụng thư viện Boost.Pulum Container hoặc lưu trữ các con trỏ thông minh sở hữu chung trong Container. Trong mã quan trọng về hiệu năng, bạn có thể xem xét việc tránh cả hai thứ này để ủng hộ các thùng chứa xâm nhập, chẳng hạn như những thứ trong Boost.Intrusive.

Nền tảng mục tiêu không thực sự ảnh hưởng đến quyết định của bạn quá nhiều. Các thiết bị nhúng, điện thoại thông minh, điện thoại câm, PC và bảng điều khiển đều có thể chạy mã tốt. Các yêu cầu của dự án như ngân sách bộ nhớ nghiêm ngặt hoặc không phân bổ động bao giờ / sau khi tải là những mối quan tâm hợp lệ hơn và sẽ ảnh hưởng đến các lựa chọn của bạn.


3
Xử lý ngoại lệ trên bảng điều khiển có thể hơi tinh ranh - đặc biệt XDK là loại ngoại lệ.
Crashworks

1
Nền tảng mục tiêu thực sự sẽ ảnh hưởng đến thiết kế của bạn. Phần cứng biến đổi dữ liệu của bạn đôi khi có thể có ảnh hưởng lớn đến mã nguồn của bạn. Kiến trúc PS3 là một ví dụ cụ thể, nơi bạn thực sự cần đưa phần cứng vào thiết kế quản lý tài nguyên và bộ nhớ cũng như trình kết xuất của bạn.
Simon

Tôi chỉ không đồng ý một chút, đặc biệt liên quan đến GC. Hầu hết thời gian, các tham chiếu theo chu kỳ không phải là một vấn đề đối với các lược đồ được tính tham chiếu. Nói chung, các vấn đề sở hữu theo chu kỳ này xuất hiện do mọi người không nghĩ đúng về quyền sở hữu đối tượng. Chỉ vì một đối tượng cần trỏ đến một cái gì đó, không có nghĩa là nó sẽ sở hữu con trỏ đó. Ví dụ thường được trích dẫn là con trỏ ngược trong cây, nhưng cha mẹ cho con trỏ trong cây có thể là con trỏ thô mà không làm mất đi sự an toàn.
Tim Seguine

4

Nếu bạn đang sử dụng C ++ 0x, hãy sử dụng std::unique_ptr<T>.

Nó không có chi phí hoạt động, không giống như std::shared_ptr<T>có chi phí tham chiếu. Unique_ptr sở hữu con trỏ của nó và bạn có thể chuyển quyền sở hữu xung quanh với ngữ nghĩa di chuyển của C ++ 0x . Bạn không thể sao chép chúng - chỉ di chuyển chúng.

Nó cũng có thể được sử dụng trong các thùng chứa, ví dụ std::vector<std::unique_ptr<T>>, tương thích nhị phân và giống hệt về hiệu năng std::vector<T*>, nhưng sẽ không rò rỉ bộ nhớ nếu bạn xóa các phần tử hoặc xóa vectơ. Điều này cũng có khả năng tương thích tốt hơn với các thuật toán STL hơn ptr_vector.

IMO cho nhiều mục đích, đây là một công cụ chứa lý tưởng: truy cập ngẫu nhiên, an toàn ngoại lệ, ngăn rò rỉ bộ nhớ, chi phí thấp cho việc phân bổ lại véc tơ (chỉ xáo trộn xung quanh con trỏ phía sau hậu trường). Rất hữu ích cho nhiều mục đích.


3

Đó là cách thực hành tốt để ghi lại những lớp nào sở hữu con trỏ. Tốt hơn là, bạn chỉ sử dụng các đối tượng bình thường và không có con trỏ bất cứ khi nào bạn có thể.

Tuy nhiên, khi bạn cần theo dõi các tài nguyên, chuyển con trỏ là lựa chọn duy nhất. Có một số trường hợp:

  • Bạn nhận được con trỏ từ một nơi khác, nhưng không quản lý nó: chỉ sử dụng một con trỏ bình thường và ghi lại nó để không có người viết mã sau khi bạn cố gắng xóa nó.
  • Bạn nhận được con trỏ từ một nơi khác và bạn theo dõi nó: sử dụng scoped_ptr.
  • Bạn nhận được con trỏ từ một nơi khác và bạn theo dõi nó nhưng nó cần một phương thức đặc biệt để xóa nó: sử dụng shared_ptr với một phương thức xóa tùy chỉnh.
  • Bạn cần con trỏ trong bộ chứa STL: nó sẽ được sao chép xung quanh vì vậy bạn cần boost :: shared_ptr.
  • Nhiều lớp chia sẻ con trỏ và không rõ ai sẽ xóa nó: shared_ptr (trường hợp trên thực sự là trường hợp đặc biệt của điểm này).
  • Bạn tự tạo con trỏ và chỉ bạn cần nó: nếu bạn thực sự không thể sử dụng một đối tượng bình thường: scoped_ptr.
  • Bạn tạo con trỏ và sẽ chia sẻ nó với các lớp khác: shared_ptr.
  • Bạn tạo con trỏ và vượt qua nó: sử dụng một con trỏ bình thường và ghi lại giao diện của bạn để chủ sở hữu mới biết rằng anh ta nên tự quản lý tài nguyên!

Tôi nghĩ rằng khá nhiều bao gồm cách tôi quản lý tài nguyên của mình ngay bây giờ. Chi phí bộ nhớ của một con trỏ như shared_ptr thường gấp đôi chi phí bộ nhớ của một con trỏ bình thường. Tôi không nghĩ rằng chi phí này quá lớn, nhưng nếu bạn thiếu tài nguyên, bạn nên xem xét việc thiết kế trò chơi của mình để giảm số lượng con trỏ thông minh. Trong các trường hợp khác, tôi chỉ thiết kế theo các nguyên tắc tốt như các viên đạn ở trên và trình hồ sơ sẽ cho tôi biết nơi tôi sẽ cần nhiều tốc độ hơn.


1

Khi nói đến việc tăng cường con trỏ cụ thể, tôi nghĩ rằng chúng nên được tránh miễn là việc thực hiện chúng không chính xác là những gì bạn cần. Họ đến với chi phí lớn hơn bất kỳ ai mong đợi ban đầu. Họ cung cấp một giao diện cho phép bạn bỏ qua các phần quan trọng và quan trọng trong bộ nhớ và quản lý nguồn lực của bạn.

Khi nói đến bất kỳ sự phát triển phần mềm nào, tôi nghĩ rằng điều quan trọng là phải suy nghĩ về dữ liệu của bạn. Điều rất quan trọng là làm thế nào dữ liệu của bạn được thể hiện trong bộ nhớ. Lý do cho điều này là tốc độ CPU đã tăng với tốc độ lớn hơn nhiều so với thời gian truy cập bộ nhớ. Điều này thường làm cho bộ nhớ lưu trữ trở thành nút cổ chai chính của hầu hết các trò chơi máy tính hiện đại. Bằng cách để dữ liệu của bạn được căn chỉnh tuyến tính trong bộ nhớ theo thứ tự truy cập sẽ thân thiện hơn với bộ đệm. Loại giải pháp này thường dẫn đến các thiết kế sạch hơn, mã đơn giản hơn và mã chắc chắn dễ gỡ lỗi hơn. Con trỏ thông minh dễ dẫn đến việc phân bổ tài nguyên bộ nhớ động thường xuyên, điều này khiến chúng bị phân tán khắp bộ nhớ.

Đây không phải là một tối ưu hóa sớm, đó là một quyết định lành mạnh có thể và nên được đưa ra càng sớm càng tốt. Đó là một câu hỏi về sự hiểu biết kiến ​​trúc về phần cứng mà phần mềm của bạn sẽ chạy và nó rất quan trọng.

Chỉnh sửa: Có một vài điều cần xem xét về hiệu suất của các con trỏ được chia sẻ:

  • Bộ đếm tham chiếu là đống phân bổ.
  • Nếu bạn sử dụng tính năng an toàn luồng, tính năng tham chiếu được thực hiện thông qua các hoạt động lồng vào nhau.
  • Truyền con trỏ theo giá trị sẽ sửa đổi số tham chiếu, có nghĩa là các hoạt động được lồng vào nhau rất có thể sử dụng truy cập ngẫu nhiên trong bộ nhớ (khóa + có khả năng bỏ lỡ bộ đệm).

2
Bạn mất tôi ở 'tránh bằng mọi giá.' Sau đó, bạn tiếp tục mô tả một loại tối ưu hóa hiếm khi là mối quan tâm cho các trò chơi trong thế giới thực. Hầu hết sự phát triển trò chơi được đặc trưng bởi các vấn đề phát triển (chậm trễ, lỗi, khả năng chơi, v.v.) không phải do thiếu hiệu năng bộ đệm CPU. Vì vậy, tôi hoàn toàn không đồng ý với ý kiến ​​cho rằng lời khuyên này không phải là tối ưu hóa sớm.
kevin42

2
Tôi phải đồng ý với thiết kế sớm của bố trí dữ liệu. Điều quan trọng là có được bất kỳ hiệu suất nào từ một bảng điều khiển / thiết bị di động hiện đại và là thứ không bao giờ được nhìn quá mức.
Olly

1
Đây là một vấn đề mà tôi đã thấy ở một trong những xưởng phim AAA mà tôi đang làm việc. Bạn cũng có thể nghe Kiến trúc sư trưởng tại Insomniac Games, Mike Acton. Tôi không nói rằng boost là một thư viện tồi, nó không chỉ phù hợp với các game có hiệu năng cao.
Simon

1
@ kevin42: Sự kết hợp bộ nhớ cache có lẽ là nguồn chính của tối ưu hóa cấp thấp trong phát triển trò chơi hiện nay. @Simon: Hầu hết các triển khai shared_ptr đều tránh các khóa trên bất kỳ nền tảng nào hỗ trợ so sánh và trao đổi, bao gồm cả Linux và PC Windows và tôi tin rằng có cả Xbox.

1
@Joe Wreschnig: Điều đó đúng, bộ nhớ cache rất có thể mặc dù gây ra bất kỳ khởi tạo nào của một con trỏ dùng chung (sao chép, tạo từ con trỏ yếu, v.v.). Lỗi bộ nhớ cache L2 trên PC hiện đại giống như 200 chu kỳ và trên PPC (xbox360 / ps3) thì cao hơn. Với một trò chơi căng thẳng, bạn có thể có tới 1000 đối tượng trò chơi, với điều kiện là mỗi đối tượng trò chơi có thể có khá nhiều tài nguyên chúng tôi đang xem xét các vấn đề trong đó quy mô của chúng là mối quan tâm chính. Điều này có thể sẽ gây ra sự cố ở cuối chu kỳ phát triển (khi bạn sẽ đạt được số lượng lớn đối tượng trò chơi).
Simon

0

Tôi có xu hướng sử dụng con trỏ thông minh ở khắp mọi nơi. Tôi không chắc đây có phải là một ý tưởng hoàn toàn tốt hay không, nhưng tôi lười biếng và tôi không thể thấy bất kỳ nhược điểm thực sự nào [ngoại trừ nếu tôi muốn thực hiện một số phép tính con trỏ kiểu C]. Tôi sử dụng boost :: shared_ptr vì tôi biết tôi có thể sao chép nó xung quanh - nếu hai thực thể chia sẻ một hình ảnh, thì nếu một thực thể khác thì cũng không nên mất hình ảnh.

Nhược điểm của điều này là nếu một đối tượng xóa thứ gì đó mà nó trỏ đến và sở hữu, nhưng một thứ khác cũng đang chỉ vào nó, thì nó sẽ không bị xóa.


1
Tôi cũng đã sử dụng share_ptr ở khắp mọi nơi - nhưng hôm nay tôi cố gắng suy nghĩ liệu tôi có thực sự cần quyền sở hữu chung cho một số dữ liệu hay không. Nếu không, có thể hợp lý để biến dữ liệu đó thành thành viên không phải con trỏ vào cấu trúc dữ liệu cha. Tôi thấy rằng quyền sở hữu rõ ràng đơn giản hóa các thiết kế.
jmp97

0

Lợi ích của việc quản lý bộ nhớ và tài liệu được cung cấp bởi con trỏ thông minh tốt có nghĩa là tôi sử dụng chúng thường xuyên. Tuy nhiên, khi trình hồ sơ kết nối và cho tôi biết một cách sử dụng cụ thể đang khiến tôi phải trả giá, tôi sẽ quay trở lại quản lý con trỏ mới hơn.


0

Tôi già, oldskool, và một bộ đếm chu kỳ. Trong công việc của riêng tôi, tôi sử dụng các con trỏ thô và không có phân bổ động khi chạy (ngoại trừ chính các pool). Mọi thứ đều được gộp lại, và quyền sở hữu rất nghiêm ngặt và không bao giờ có thể chuyển nhượng được, nếu thực sự cần thiết tôi viết một bộ cấp phát khối nhỏ tùy chỉnh. Tôi chắc chắn rằng có một trạng thái trong trò chơi cho mọi hồ bơi để tự xóa. Khi mọi thứ có lông, tôi sẽ bọc các đối tượng trong tay cầm để tôi có thể di chuyển chúng, nhưng tôi thì không. Container là tùy chỉnh và xương trần vô cùng. Tôi cũng không sử dụng lại mã.
Mặc dù tôi sẽ không bao giờ tranh luận về đức tính của tất cả các con trỏ và bộ chứa và trình lặp thông minh và không có gì khác, tôi được biết là có thể viết mã cực nhanh (và đáng tin cậy một cách hợp lý - mặc dù không nên cho người khác nhảy vào mã của tôi vì những lý do rõ ràng, như những cơn đau tim và những cơn ác mộng vĩnh viễn).

Trong công việc, tất nhiên, tất cả đều khác biệt, trừ khi tôi đang tạo mẫu, điều mà tôi may mắn được làm rất nhiều.


0

Hầu như không có mặc dù điều này được thừa nhận là một câu trả lời lạ, và có lẽ không nơi nào phù hợp với tất cả mọi người.

Nhưng tôi đã thấy nó hữu ích hơn rất nhiều trong trường hợp cá nhân của tôi để lưu trữ tất cả các trường hợp của một loại cụ thể trong chuỗi truy cập ngẫu nhiên trung tâm (an toàn luồng) và thay vào đó để làm việc với các chỉ mục 32 bit (ví dụ: địa chỉ tương đối) , thay vì con trỏ tuyệt đối.

Cho một sự khởi đầu:

  1. Nó giảm một nửa yêu cầu bộ nhớ của con trỏ tương tự trên nền tảng 64 bit. Cho đến nay tôi chưa bao giờ cần nhiều hơn ~ 4,29 tỷ phiên bản của một loại dữ liệu cụ thể.
  2. Nó đảm bảo rằng tất cả các phiên bản của một loại cụ thể T, sẽ không bao giờ quá phân tán trong bộ nhớ. Điều đó có xu hướng giảm các lỗi bộ nhớ cache cho tất cả các loại mẫu truy cập, thậm chí di chuyển ngang qua các cấu trúc được liên kết như cây nếu các nút được liên kết với nhau bằng các chỉ mục thay vì con trỏ.
  3. Dữ liệu song song trở nên dễ dàng liên kết bằng cách sử dụng mảng song song giá rẻ (hoặc mảng thưa) thay vì cây hoặc bảng băm.
  4. Đặt giao điểm có thể được tìm thấy trong thời gian tuyến tính hoặc tốt hơn bằng cách sử dụng một bitet song song.
  5. Chúng ta có thể sắp xếp các chỉ mục và có được một mẫu truy cập tuần tự rất thân thiện với bộ đệm.
  6. Chúng tôi có thể theo dõi xem có bao nhiêu trường hợp loại dữ liệu cụ thể đã được phân bổ.
  7. Giảm thiểu số lượng địa điểm phải đối phó với những thứ như ngoại lệ - an toàn, nếu bạn quan tâm đến loại điều đó.

Điều đó nói rằng, thuận tiện là một nhược điểm cũng như an toàn loại. Chúng tôi không thể truy cập một thể hiện Tmà không có quyền truy cập vào cả vùng chứa chỉ mục. Và một cái cũ đơn giản int32_tcho chúng ta không có gì về loại dữ liệu mà nó đề cập đến, vì vậy không có loại an toàn. Chúng tôi có thể vô tình cố gắng truy cập Barbằng cách sử dụng một chỉ mục để Foo. Để giảm thiểu vấn đề thứ hai, tôi thường làm điều này:

struct FooIndex
{
    int32_t index;
};

Điều này có vẻ hơi ngớ ngẩn nhưng nó mang lại cho tôi sự an toàn kiểu để mọi người không thể vô tình cố gắng truy cập Barthông qua một chỉ mục Foomà không có lỗi trình biên dịch. Về mặt thuận tiện, tôi chỉ chấp nhận sự bất tiện nhỏ.

Một điều khác có thể gây bất tiện lớn cho mọi người là tôi không thể sử dụng đa hình dựa trên kiểu thừa kế theo kiểu OOP, vì điều đó sẽ gọi một con trỏ cơ sở có thể trỏ đến tất cả các loại phụ khác nhau với các yêu cầu căn chỉnh và kích thước khác nhau. Nhưng tôi không sử dụng thừa kế nhiều trong những ngày này - thích cách tiếp cận ECS.

Đối với shared_ptr, tôi cố gắng không sử dụng nó rất nhiều. Hầu hết thời gian tôi không thấy hợp lý khi chia sẻ quyền sở hữu và làm như vậy một cách ngớ ngẩn có thể dẫn đến rò rỉ logic. Thường thì ít nhất là ở cấp độ cao, một thứ có xu hướng thuộc về một thứ. Nơi tôi thường thấy nó hấp dẫn khi sử dụng shared_ptrlà kéo dài tuổi thọ của một đối tượng ở những nơi không thực sự giải quyết quyền sở hữu, giống như chỉ là một chức năng cục bộ trong một luồng để đảm bảo đối tượng không bị phá hủy trước khi kết thúc luồng sử dụng nó.

Để giải quyết vấn đề đó, thay vì sử dụng shared_ptrhoặc GC hoặc bất cứ thứ gì tương tự, tôi thường ưu tiên các tác vụ ngắn hạn chạy từ nhóm luồng và làm như vậy nếu luồng đó yêu cầu phá hủy một đối tượng, thì sự hủy diệt thực sự được hoãn lại an toàn thời gian khi hệ thống có thể đảm bảo rằng không có luồng nào cần truy cập loại đối tượng đã nói.

Đôi khi tôi vẫn kết thúc bằng cách sử dụng tính năng giới thiệu nhưng coi nó như một chiến lược cuối cùng. Và có một vài trường hợp thực sự có ý nghĩa để chia sẻ quyền sở hữu, như việc thực hiện cấu trúc dữ liệu liên tục, và ở đó tôi thấy nó có ý nghĩa hoàn hảo để tiếp cận shared_ptrngay lập tức.

Vì vậy, dù sao, tôi chủ yếu sử dụng các chỉ số, và sử dụng cả con trỏ thô và thông minh một cách tiết kiệm. Tôi thích các chỉ số và các loại cửa chúng mở ra khi bạn biết các đối tượng của mình được lưu trữ liên tục và không nằm rải rác trong không gian bộ nhớ.

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.