2012-05-17 37 views
61

Giả sử tôi có một lớp học với phương thức trả về shared_ptr.Làm thế nào để trả lại con trỏ thông minh (shared_ptr), bằng cách tham chiếu hoặc theo giá trị?

Những lợi ích và nhược điểm có thể có của việc trả lại bằng tham chiếu hoặc theo giá trị là gì?

Hai manh mối có thể:

  • sớm hủy diệt đối tượng. Nếu tôi trả về tham chiếu (const) shared_ptr bởi (const), bộ đếm tham chiếu không được tăng lên, vì vậy, tôi có nguy cơ bị xóa đối tượng khi nó nằm ngoài phạm vi trong ngữ cảnh khác (ví dụ: một chuỗi khác). Điều này có đúng không? Điều gì xảy ra nếu môi trường là đơn luồng, tình huống này có thể xảy ra không?
  • Chi phí. Vượt qua giá trị chắc chắn không phải là miễn phí. Có đáng để tránh nó bất cứ khi nào có thể?

Cảm ơn mọi người.

Trả lời

80

Trả về con trỏ thông minh theo giá trị.

Như bạn đã nói, nếu bạn trả lại bằng tham chiếu, bạn sẽ không tăng số lượng tham chiếu một cách chính xác, điều này sẽ mở ra nguy cơ xóa nội dung nào đó vào thời điểm không thích hợp. Điều đó một mình nên là đủ lý do để không trở lại bằng cách tham khảo. Giao diện nên mạnh mẽ.

Mối quan tâm về chi phí hiện nay là nhờ vào return value optimization (RVO), vì vậy bạn sẽ không phải chịu một chuỗi gia tăng hoặc giảm dần hoặc tương tự như vậy trong trình biên dịch hiện đại. Vì vậy, cách tốt nhất để trả lại một shared_ptr là chỉ cần quay trở lại theo giá trị:

shared_ptr<T> Foo() 
{ 
    return shared_ptr<T>(/* acquire something */); 
}; 

Đây là cơ hội RVO dead-rõ ràng cho trình biên dịch C++ hiện đại. Tôi biết một thực tế là trình biên dịch Visual C++ thực hiện RVO ngay cả khi tất cả các tối ưu hóa được tắt. Và với ngữ nghĩa di chuyển của C++ 11, mối quan tâm này thậm chí còn ít liên quan hơn. (Nhưng cách duy nhất để chắc chắn là hồ sơ và thử nghiệm.)

Nếu bạn vẫn không bị thuyết phục, Dave Abrahams có số an article làm cho đối số trả về theo giá trị. Tôi tái tạo một đoạn trích ở đây; Tôi khuyên bạn nên đọc toàn bộ bài viết:

Hãy trung thực: mã sau làm bạn cảm thấy thế nào?

std::vector<std::string> get_names(); 
... 
std::vector<std::string> const names = get_names(); 

Thẳng thắn, mặc dù tôi nên biết rõ hơn, điều đó khiến tôi lo lắng. Về nguyên tắc, khi get_names() trả về, chúng tôi phải sao chép số vector trong số string giây. Sau đó, chúng tôi cần sao chép lại khi chúng tôi khởi tạo names và chúng tôi cần hủy bản sao đầu tiên. Nếu có N string s trong vectơ, mỗi bản sao có thể yêu cầu nhiều phân bổ bộ nhớ N + 1 và toàn bộ truy cập dữ liệu không thân thiện với bộ đệm ẩn> khi nội dung chuỗi được sao chép.

Thay vì đối đầu mà loại lo lắng, tôi đã thường rơi trở lại trên pass-by-reference để tránh bản sao không cần thiết:

get_names(std::vector<std::string>& out_param); 
... 
std::vector<std::string> names; 
get_names(names); 

Thật không may, phương pháp này là xa lý tưởng.

  • Mã này tăng 150%
  • Chúng tôi đã phải thả const -ness bởi vì chúng ta đang biến đổi tên.
  • lập trình viên Như chức năng muốn nhắc nhở chúng ta, đột biến làm cho mã phức tạp hơn đối với lý do về bằng cách phá hoại minh bạch tham chiếu và lý luận equational.
  • Chúng tôi không còn có ngữ nghĩa giá trị nghiêm ngặt cho tên.

Nhưng có thực sự cần thiết để làm hỏng mã của chúng tôi theo cách này để đạt được hiệu quả không? May mắn thay, câu trả lời hóa ra là không (và đặc biệt là nếu bạn không sử dụng C++ 0x).

+0

Tôi không biết rằng tôi có thể nói RVO làm cho câu hỏi tranh luận kể từ khi trở bằng cách tham khảo decidedly làm RVO không thể. –

+0

@CrazyEddie: Đúng vậy, đó là một trong những lý do tôi khuyên OP trả lại theo giá trị. –

+0

Quy tắc RVO có được phép theo tiêu chuẩn hay không, vượt qua các quy tắc về đồng bộ hóa/xảy ra trước các mối quan hệ, được đảm bảo theo tiêu chuẩn? –

17

Về bất kỳ con trỏ thông minh (không chỉ shared_ptr), tôi không nghĩ rằng đó là bao giờ có thể chấp nhận để trả về một tham chiếu đến một, và tôi sẽ rất ngần ngại để vượt qua chúng xung quanh bằng cách tham chiếu hoặc con trỏ thô. Tại sao? Bởi vì bạn không thể chắc chắn rằng nó sẽ không được sao chép nông thông qua một tài liệu tham khảo sau này. Điểm đầu tiên của bạn xác định lý do tại sao điều này phải là một mối quan tâm. Điều này có thể xảy ra ngay cả trong môi trường đơn luồng. Bạn không cần truy cập đồng thời vào dữ liệu để đặt ngữ nghĩa sao chép xấu trong các chương trình của bạn. Bạn không thực sự kiểm soát những gì người dùng của bạn thực hiện với con trỏ khi bạn bỏ qua, vì vậy, đừng khuyến khích sử dụng sai khiến người dùng API của bạn có đủ dây để treo cổ.

Thứ hai, nhìn vào thực hiện con trỏ thông minh của bạn, nếu có thể. Xây dựng và tiêu hủy nên darn gần không đáng kể. Nếu chi phí này không được chấp nhận, thì đừng sử dụng con trỏ thông minh! Nhưng ngoài điều này, bạn cũng sẽ cần phải kiểm tra kiến ​​trúc đồng thời mà bạn đã có, vì việc truy cập lẫn nhau độc quyền vào cơ chế theo dõi việc sử dụng con trỏ sẽ làm chậm bạn hơn là xây dựng đối tượng shared_ptr.

Chỉnh sửa, 3 năm sau: với sự xuất hiện của các tính năng hiện đại hơn trong C++, tôi sẽ tinh chỉnh câu trả lời của mình để chấp nhận các trường hợp khi bạn đã viết một lambda chưa bao giờ sống bên ngoài phạm vi chức năng gọi, và không được sao chép ở một nơi khác. Ở đây, nếu bạn muốn tiết kiệm chi phí rất nhỏ của việc sao chép một con trỏ được chia sẻ, nó sẽ là công bằng và an toàn. Tại sao? Bởi vì bạn có thể đảm bảo rằng tham chiếu sẽ không bao giờ bị sử dụng sai.

Các vấn đề liên quan