2013-03-01 37 views
5

Đây là vấn đề của tôi. Tôi có chức năng lấy struct làm đầu vào, phân bổ struct mới và sau đó trả về. struct có các nội dung sauTrả về một đối tượng struct lớn theo giá trị hoặc con trỏ tới phân bổ động trên C++

struct Data { 
std::vector<custom_type> vector_vars; 
std::string key; 
std::map<std::string, Data*> index; 
}; 

vector_vars có kích thước 500-1000. custom_type nếu có liên quan là this class. index giúp tôi truy cập khác nhau Data struct theo key. Đây là hàm.

Data func(Data inputData) { 

Data outputData; 
//compare values with inputData 
//change some values in Data struct 
return outputData 
} 
  1. Tôi có sử dụng phân bổ chồng và gửi lại để nó được sao chép vào RHS. Điều này có được khuyến khích vì việc sao chép có thể tạo ra rất nhiều chi phí không?

  2. Tôi có thể trả lại tham chiếu/con trỏ tới cấu trúc được phân bổ trên ngăn xếp trong một hàm không?

  3. Bạn nên sử dụng phân bổ động thay vì ở đây (có thể với std::shared_ptr) để có hiệu quả?

+0

Bạn có cần 'outputData' khác với' inputData' không? Nếu có, hãy phân bổ trên ngăn xếp và trả về theo giá trị. – juanchopanza

+1

2 chắc chắn là không hợp lệ. Bạn có thể đo lường cách thức 1 và 3 thực hiện cho dữ liệu cụ thể của bạn và quyết định cho chính mình, đặt cược của tôi là ngày trở lại theo giá trị (trình biên dịch sẽ tối ưu hóa việc sao chép). Có lẽ bạn nên vượt qua đối số hàm bằng tham chiếu. –

+0

'1' thường là chính xác; tất cả các trình biên dịch hiện đại thực hiện RVO, và trong C++ 11 ngay cả khi RVO không thể, giá trị trả về sẽ được di chuyển. – ecatmur

Trả lời

15
  1. Trả về đối tượng theo giá trị. Sao chép elision là một tối ưu hóa mà tiêu chuẩn cho phép trình biên dịch để làm cho rằng loại bỏ bản sao không cần thiết. Trong C++ 03, bản sao hầu như chắc chắn sẽ được trình biên dịch ưu tú. Trong C++ 11, bản sao đầu tiên sẽ được coi là một di chuyển (mà sẽ lần lượt di chuyển nội dung của nó) và thậm chí có thể được elided.

    trong câu lệnh return trong hàm có loại trả về lớp, khi biểu thức là tên của đối tượng tự động không bay hơi (ngoài hàm hoặc tham số mệnh đề bắt buộc) với cùng loại cv không đủ điều kiện như kiểu trả về hàm, thao tác sao chép/di chuyển có thể được bỏ qua bằng cách xây dựng đối tượng tự động trực tiếp vào giá trị trả về của hàm

    Cụ thể, khi tiêu chí này được gọi là tối ưu hóa giá trị trả về , một hình thức return value optimization.

    Vì vậy, tùy chọn thành ngữ và thân thiện nhất ở đây là trả về theo giá trị. Tất nhiên, nếu bạn đo rằng ngay cả khi tối ưu hóa trả lại theo giá trị là một vấn đề, thì chắc chắn, bạn có thể xem xét phân bổ động (xem điểm 3).

  2. Không, bạn không nên trả lại tham chiếu hoặc con trỏ đến biến cục bộ. Đối tượng có thời lượng lưu trữ tự động (những gì bạn gọi là phân bổ trên ngăn xếp) sẽ không còn tồn tại nữa.Tham chiếu hoặc con trỏ sẽ bị treo lơ lửng. Sử dụng nó theo bất kỳ cách nào có ý nghĩa sẽ dẫn đến hành vi không xác định.

  3. Không, như đã đề cập ở trên, bạn nên trả lại theo giá trị. Tuy nhiên, nếu bạn thực sự, thực sự cần, bạn có thể phân bổ động Data và trả lại bằng con trỏ thông minh. Con trỏ thông minh được ưu tiên ở đây sẽ là std::unique_ptr vì chức năng của bạn đang chuyển quyền sở hữu cho chức năng gọi điện (không chia sẻ quyền sở hữu).

+0

Cảm ơn bạn đã giải thích chi tiết như vậy. Giao diện 'std :: map' như thế nào? Nếu nó là 'std :: map ' hoặc 'std :: map '? – user592748

+1

@ user592748 Nó phụ thuộc vào việc bản đồ nên đề cập đến cùng một đối tượng như ở mọi nơi khác hoặc nếu một bản sao là tốt. Điều đó phụ thuộc vào vấn đề. –

2
  1. Trên thực tế, một trong hai hoạt động nhanh có thể xảy ra ở đây: hoặc outputData có thể được di chuyển vào giá trị trả về, hoặc di chuyển này có thể được elided. Trong mọi trường hợp sẽ không có bất kỳ bản sao đắt tiền nào. Bản sao của inputData có vẻ không cần thiết, có lẽ bạn nên chuyển nó thành Data const& inputData.
  2. Bạn có thể thực hiện việc này nhưng sẽ dẫn đến hành vi không xác định.
  3. Không. Phân bổ động có thể ít an toàn hơn, chậm hơn, khó hiểu hơn và thường tồi tệ hơn.
+0

+1, nhưng tôi nghĩ rõ ràng hơn là bản sao có thể được elided * hoặc * nếu không, nó vẫn có thể được di chuyển. Elision át lên. – juanchopanza

2
Data func1(Data inputData) { 
    Data outputData; 
    return outputData 
} 

mất đối tượng của loại Data bởi giá trị và tạo ra một thể hiện của Data mà sau đó được trả lại theo giá trị. Vì vậy, bản sao được tạo ra không chỉ khi outputData đang được trả lại, mà còn khi nhận được inputData. Đừng lo lắng về hiệu suất khi trả lại theo giá trị, có các cơ chế sẽ xử lý nó (đáng kể để đề cập đến sao chép elision và có thể tối ưu hóa giá trị trả lại). Tôi khuyên bạn nên để pas inputData bằng cách tham khảo mặc dù: Data func1(const Data& inputData).

Điều này có được khuyến khích vì việc sao chép có thể tạo ra rất nhiều chi phí không?

Quy tắc chung: Đừng lo lắng về hiệu suất mã của bạn trừ khi bạn gặp phải vấn đề về hiệu suất.

Tôi có thể trả về tham chiếu/con trỏ đến cấu trúc được phân bổ trên ngăn xếp trong một hàm không?

Không trả về tham chiếu/con trỏ đến đối tượng có thời lượng lưu trữ tự động được xác định trong phạm vi chức năng đó. Các đối tượng được destructed khi thực hiện đi ra khỏi phạm vi, vì vậy bạn sẽ kết thúc với treo lủng lẳng con trỏ (tài liệu tham khảo không hợp lệ) mà sẽ dẫn đến hành vi undefined

Có nên sử dụng phân bổ động thay vào đó ở đây để có hiệu quả?

Tránh phân bổ động bất cứ khi nào có thể. Không phải vì hiệu quả, mà vì lợi ích của việc quản lý bộ nhớ trong mã của bạn. Theo dõi RAII idiom và tránh xử lý việc quản lý bộ nhớ xấu xí của chính bạn.

+0

Cảm ơn bạn rất nhiều vì đã chỉ ra quy tắc ngón tay cái và thành ngữ lập trình – user592748

+0

@ user592748: Bạn được chào đón :) Tối ưu hóa sớm không bao giờ tốt. – LihO

1

Bạn có thể viết lại func() để trả lại Data* thay vì Data. Chắc chắn sẽ nhanh hơn khi sao chép số Data* hơn là sao chép số Data. Nhưng trước đó: bạn có quan tâm không? Chức năng này có chạy một lần trong khi khởi tạo chương trình hay không, lấy một phần mười giây của đồng hồ treo tường, hay bạn có thể gọi nó là một khung hình hay không?

Khi bạn đang viết một chương trình hoặc tối ưu hóa chương trình hiện tại, mục tiêu của bạn phải luôn là làm cho nó "đủ nhanh" (và "đủ đáp ứng", nhưng đừng bận tâm đến bây giờ). Định nghĩa "đủ nhanh" thay đổi rất nhiều: trong một mô phỏng, bạn có thể vui vẻ làm hỏng một terabyte dữ liệu mỗi giờ, trong khi trong trò chơi video, bạn cần hiển thị cho người chơi một khung đồ họa mới cứ sau 16 mili giây.

Vì vậy: chương trình của bạn "đủ nhanh" chỉ với phân bổ ngăn xếp? Nếu có, hãy để nó một mình. Nếu không, hãy sử dụng phân bổ động thay thế. Nói chung, đó là phong cách C++ tốt để trả về con trỏ thay vì cấu trúc lớn, nhưng bạn không nên lo lắng về điều đó trong khi bạn vẫn đang học. Làm cho nó hoạt động, làm cho nó đúng, sau đó làm cho nó nhanh.

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