Tôi có nên lưu trữ toàn bộ các đối tượng, hoặc con trỏ đến các đối tượng trong các thùng chứa?


162

Thiết kế một hệ thống mới từ đầu. Tôi sẽ sử dụng STL để lưu trữ danh sách và bản đồ của một số đối tượng tồn tại lâu dài.

Câu hỏi: Tôi có nên đảm bảo các đối tượng của mình có các hàm tạo sao chép và lưu trữ các bản sao của các đối tượng trong các thùng chứa STL của tôi hay nói chung là tốt hơn để tự quản lý cuộc sống & phạm vi và chỉ lưu trữ các con trỏ vào các đối tượng đó trong các thùng chứa STL của tôi?

Tôi nhận ra điều này hơi ngắn về chi tiết, nhưng tôi đang tìm câu trả lời tốt hơn "về mặt lý thuyết" nếu nó tồn tại, vì tôi biết cả hai giải pháp này đều khả thi.

Hai nhược điểm rất rõ ràng khi chơi với con trỏ: 1) Tôi phải tự mình quản lý phân bổ / phân bổ các đối tượng này trong phạm vi ngoài STL. 2) Tôi không thể tạo một đối tượng tạm thời trên ngăn xếp và thêm nó vào các thùng chứa của mình.

Có điều gì khác tôi đang thiếu?


36
chúa ơi tôi thích trang này, đây là câu hỏi CHÍNH XÁC mà tôi đã nghĩ đến hôm nay ... cảm ơn vì đã làm công việc hỏi nó cho tôi :-)
eviljack

2
Một điều thú vị khác là chúng ta nên kiểm tra xem con trỏ có thực sự được thêm vào bộ sưu tập hay không và nếu không có khả năng chúng ta nên gọi xóa để tránh rò rỉ bộ nhớ ... if ((set.insert (con trỏ)). second = false) {xóa con trỏ;}
javapowered

Câu trả lời:


68

Vì mọi người đang chú ý đến hiệu quả của việc sử dụng con trỏ.

Nếu bạn đang xem xét sử dụng một std :: vector và nếu các bản cập nhật ít và bạn thường lặp đi lặp lại trên bộ sưu tập của mình và thì "bản sao" đối tượng lưu trữ không đa hình sẽ hiệu quả hơn vì bạn sẽ có được địa phương tham chiếu tốt hơn.

Otoh, nếu cập nhật là con trỏ lưu trữ phổ biến sẽ tiết kiệm chi phí sao chép / di chuyển.


7
Về mặt địa phương bộ đệm, lưu trữ con trỏ vào vector có thể hiệu quả nếu được sử dụng cùng với bộ cấp phát tùy chỉnh cho các điểm. Bộ cấp phát tùy chỉnh phải chăm sóc địa phương bộ đệm, ví dụ như sử dụng vị trí mới (xem en.wikipedia.org/wiki/Plocation_syntax#Custom_allocators ).
amit

47

Điều này thực sự phụ thuộc vào tình hình của bạn.

Nếu các đối tượng của bạn nhỏ và thực hiện một bản sao của đối tượng thì nhẹ, thì việc lưu trữ dữ liệu bên trong một thùng chứa stl rất đơn giản và dễ quản lý hơn theo quan điểm của tôi vì bạn không phải lo lắng về việc quản lý trọn đời.

Nếu các đối tượng của bạn lớn và có một hàm tạo mặc định không có ý nghĩa hoặc các bản sao của các đối tượng đắt tiền, thì việc lưu trữ với các con trỏ có lẽ là cách tốt nhất.

Nếu bạn quyết định sử dụng con trỏ tới các đối tượng, hãy xem Thư viện bộ chứa con trỏ Boost . Thư viện boost này bao bọc tất cả các container STL để sử dụng với các đối tượng được phân bổ động.

Mỗi vùng chứa con trỏ (ví dụ ptr_vector) có quyền sở hữu một đối tượng khi nó được thêm vào vùng chứa và quản lý vòng đời của các đối tượng đó cho bạn. Bạn cũng truy cập tất cả các yếu tố trong một thùng chứa ptr_ bằng cách tham khảo. Điều này cho phép bạn làm những việc như

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Các lớp này bao bọc các thùng chứa STL và làm việc với tất cả các thuật toán STL, thực sự tiện dụng.

Ngoài ra còn có các phương tiện để chuyển quyền sở hữu một con trỏ trong vùng chứa sang người gọi (thông qua chức năng phát hành trong hầu hết các container).


38

Nếu bạn đang lưu trữ các đối tượng đa hình, bạn luôn cần sử dụng một tập hợp các con trỏ lớp cơ sở.

Đó là nếu bạn có kế hoạch lưu trữ các loại dẫn xuất khác nhau trong bộ sưu tập của mình, bạn phải lưu trữ con trỏ hoặc bị ăn bởi deamon.


1
Tôi yêu các deamon cắt!
idichekop

22

Xin lỗi để nhảy trong 3 năm sau sự kiện, nhưng một lưu ý cảnh báo ở đây ...

Trong dự án lớn cuối cùng của tôi, cấu trúc dữ liệu trung tâm của tôi là một tập hợp các đối tượng khá đơn giản. Khoảng một năm trong dự án, khi các yêu cầu phát triển, tôi nhận ra rằng đối tượng thực sự cần phải là đa hình. Phải mất một vài tuần phẫu thuật não khó khăn và khó chịu để sửa cấu trúc dữ liệu thành một bộ con trỏ lớp cơ sở và để xử lý tất cả các thiệt hại tài sản thế chấp trong lưu trữ đối tượng, đúc, v.v. Phải mất vài tháng để tôi tự thuyết phục bản thân rằng mã mới đang hoạt động. Ngẫu nhiên, điều này khiến tôi suy nghĩ kỹ về mô hình đối tượng được thiết kế tốt của C ++.

Trong dự án lớn hiện tại của tôi, cấu trúc dữ liệu trung tâm của tôi là một tập hợp các đối tượng khá đơn giản. Khoảng một năm trong dự án (mà xảy ra là ngày hôm nay), tôi nhận ra rằng đối tượng thực sự cần phải là đa hình. Quay lại mạng, tìm thấy chuỗi này và tìm thấy liên kết của Nick đến thư viện thùng chứa con trỏ Boost. Đây chính xác là những gì tôi đã phải viết lần trước để sửa chữa mọi thứ, vì vậy tôi sẽ cho nó đi lần này.

Về mặt đạo đức, đối với tôi, dù sao đi nữa: nếu thông số kỹ thuật của bạn không được đúc 100%, hãy tìm kiếm con trỏ và bạn có thể có khả năng tự cứu mình rất nhiều công việc sau này.


Thông số kỹ thuật không bao giờ được đặt trong đá. Tôi không nghĩ điều đó có nghĩa là bạn nên sử dụng riêng bộ chứa con trỏ, mặc dù bộ chứa con trỏ Boost dường như làm cho tùy chọn đó hấp dẫn hơn nhiều. Tôi nghi ngờ rằng bạn cần phải đại tu toàn bộ chương trình của mình cùng một lúc nếu bạn quyết định rằng một thùng chứa đối tượng nên được chuyển đổi thành một thùng chứa con trỏ. Đây có thể là trường hợp theo một số thiết kế. Trong trường hợp đó, nó là một thiết kế dễ vỡ. Trong trường hợp đó, đừng đổ lỗi cho vấn đề của bạn về "điểm yếu" của các thùng chứa đối tượng.
mã allyour

Bạn có thể để lại vật phẩm trong vectơ với ngữ nghĩa giá trị và thực hiện hành vi đa hình bên trong.
Billy ONeal

19

Tại sao không có được thứ tốt nhất của cả hai thế giới: làm một thùng chứa con trỏ thông minh (chẳng hạn như boost::shared_ptrhoặc std::shared_ptr). Bạn không phải quản lý bộ nhớ và bạn không phải đối phó với các hoạt động sao chép lớn.


Cách tiếp cận này khác với những gì Nick Haddad đề xuất bằng cách sử dụng Thư viện bộ chứa con trỏ Boost?
Thorsten Schöning

10
@ ThorstenSchöning std :: shared_ptr không thêm phụ thuộc vào boost.
James Johnston

Bạn không thể sử dụng các con trỏ được chia sẻ để xử lý tính đa hình của mình để cuối cùng bạn sẽ bỏ lỡ tính năng này với phương pháp này, trừ khi bạn sử dụng con trỏ một cách rõ ràng
auserdude

11

Nói chung, lưu trữ các đối tượng trực tiếp trong bộ chứa STL là tốt nhất vì nó đơn giản nhất, hiệu quả nhất và dễ sử dụng nhất cho đối tượng.

Nếu bản thân đối tượng của bạn có cú pháp không thể sao chép hoặc là một loại cơ sở trừu tượng, bạn sẽ cần lưu trữ các con trỏ (dễ nhất là sử dụng shared_ptr)


4
Sẽ không hiệu quả nhất nếu các đối tượng của bạn lớn và bạn di chuyển các yếu tố xung quanh thường xuyên.
mã allyour

3

Bạn dường như có một nắm bắt tốt về sự khác biệt. Nếu các đối tượng nhỏ và dễ sao chép, thì bằng mọi cách lưu trữ chúng.

Nếu không, tôi sẽ nghĩ về việc lưu trữ các con trỏ thông minh (không phải auto_ptr, một con trỏ đếm thông minh) cho những con trỏ bạn phân bổ trên heap. Rõ ràng, nếu bạn chọn sử dụng con trỏ thông minh, thì bạn không thể lưu trữ các đối tượng được phân bổ tạm thời (như bạn đã nói).

@ Torbjorn làm cho một điểm tốt về cắt lát.


1
Ồ, và chưa bao giờ tạo ra một bộ sưu tập auto_ptr's
Torbjorn Gyllebring

Đúng, auto_ptr không phải là một con trỏ thông minh - nó không được tính.
Lou Franco

auto_ptr cũng không có ngữ nghĩa sao chép không phá hủy. Hành động chuyển nhượng một auto_ptr từ oneđể anothersẽ phát hành các tài liệu tham khảo từ onevà thay đổi one.
Andy Finkenstadt


2

Nếu các đối tượng sẽ được chuyển đến nơi khác trong mã, lưu trữ trong một vectơ boost :: shared_ptr. Điều này đảm bảo rằng các con trỏ tới đối tượng sẽ vẫn hợp lệ nếu bạn thay đổi kích thước vectơ.

I E:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Nếu không có ai khác lưu trữ các con trỏ tới các đối tượng hoặc danh sách không tăng và thu hẹp, chỉ cần lưu trữ dưới dạng các đối tượng cũ đơn giản:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

1

Câu hỏi này đã được tôi làm phiền trong một thời gian.

Tôi nghiêng về lưu trữ con trỏ, nhưng tôi có một số yêu cầu bổ sung (trình bao bọc SWIG lua) có thể không áp dụng cho bạn.

Điểm quan trọng nhất trong bài này là tự kiểm tra nó , sử dụng các đối tượng của bạn

Tôi đã làm điều này ngày hôm nay để kiểm tra tốc độ gọi hàm thành viên trên bộ sưu tập 10 triệu đối tượng, 500 lần.

Hàm cập nhật x và y dựa trên xdir và ydir (tất cả các biến thành viên float).

Tôi đã sử dụng một danh sách std :: để giữ cả hai loại đối tượng và tôi thấy rằng việc lưu trữ đối tượng trong danh sách nhanh hơn một chút so với sử dụng một con trỏ. Mặt khác, hiệu suất rất gần, do đó, nó phụ thuộc vào cách chúng sẽ được sử dụng trong ứng dụng của bạn.

Để tham khảo, với -O3 trên phần cứng của tôi, các con trỏ mất 41 giây để hoàn thành và các đối tượng thô mất 30 giây để hoàn thà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.