Lưu ý: câu trả lời này chỉ áp dụng cho c ++ 11 trở đi. Không có cái gọi là "C / C ++", chúng là các ngôn ngữ khác nhau.
Không, không có gì nguy hiểm khi trả về một đối tượng cục bộ theo giá trị, và bạn nên làm như vậy. Tuy nhiên, tôi nghĩ rằng có một điểm quan trọng bị thiếu trong tất cả các câu trả lời ở đây. Nhiều người khác đã nói rằng cấu trúc đang được sao chép hoặc được đặt trực tiếp bằng RVO. Tuy nhiên, điều này không hoàn toàn chính xác. Tôi sẽ cố gắng giải thích chính xác những điều nào có thể xảy ra khi trả về một đối tượng cục bộ.
Di chuyển ngữ nghĩa
Kể từ c ++ 11, chúng ta đã có các tham chiếu rvalue là các tham chiếu đến các đối tượng tạm thời có thể bị đánh cắp một cách an toàn. Ví dụ, std :: vector có một hàm tạo di chuyển cũng như một toán tử gán di chuyển. Cả hai đều có độ phức tạp không đổi và chỉ cần sao chép con trỏ đến dữ liệu của vectơ được di chuyển từ đó. Tôi sẽ không đi vào chi tiết hơn về ngữ nghĩa chuyển động ở đây.
Vì một đối tượng được tạo cục bộ trong một hàm là tạm thời và vượt ra ngoài phạm vi khi hàm trả về, một đối tượng được trả về sẽ không bao giờ được sao chép với c ++ 11 trở đi. Hàm khởi tạo di chuyển đang được gọi trên đối tượng được trả về (hoặc không, sẽ giải thích sau). Điều này có nghĩa là nếu bạn trả về một đối tượng bằng một hàm tạo bản sao đắt tiền nhưng hàm tạo di chuyển rẻ tiền, chẳng hạn như một vectơ lớn, thì chỉ có quyền sở hữu dữ liệu được chuyển từ đối tượng cục bộ sang đối tượng được trả về - điều này rẻ.
Lưu ý rằng trong ví dụ cụ thể của bạn, không có sự khác biệt giữa sao chép và di chuyển đối tượng. Các hàm tạo di chuyển và sao chép mặc định của cấu trúc của bạn dẫn đến các hoạt động giống nhau; sao chép hai số nguyên. Tuy nhiên, điều này ít nhất là nhanh hơn bất kỳ giải pháp nào khác vì toàn bộ cấu trúc nằm trong một thanh ghi CPU 64 bit (hãy sửa cho tôi nếu tôi sai, tôi không biết nhiều thanh ghi CPU).
RVO và NRVO
RVO có nghĩa là Tối ưu hóa Giá trị Trả lại và là một trong số rất ít các tối ưu hóa mà trình biên dịch thực hiện có thể có tác dụng phụ. Kể từ c ++ 17, RVO là bắt buộc. Khi trả về một đối tượng không tên, nó được xây dựng trực tiếp tại chỗ mà người gọi chỉ định giá trị trả về. Cả hàm tạo bản sao và hàm tạo di chuyển đều không được gọi. Nếu không có RVO, đối tượng không tên sẽ được xây dựng cục bộ đầu tiên, sau đó di chuyển được xây dựng trong địa chỉ trả về, sau đó đối tượng không tên cục bộ sẽ bị hủy.
Ví dụ trong đó RVO là bắt buộc (c ++ 17) hoặc có thể (trước c ++ 17):
auto function(int a, int b) -> MyStruct {
return MyStruct{a, b};
}
NRVO có nghĩa là Tối ưu hóa Giá trị Trả lại Được đặt tên và giống như RVO ngoại trừ nó được thực hiện cho một đối tượng được đặt tên cục bộ cho hàm được gọi. Điều này vẫn không được đảm bảo bởi tiêu chuẩn (c ++ 20) nhưng nhiều trình biên dịch vẫn làm điều đó. Lưu ý rằng ngay cả với các đối tượng cục bộ được đặt tên, chúng vẫn bị di chuyển tồi tệ nhất khi được trả lại.
Phần kết luận
Trường hợp duy nhất mà bạn nên xem xét không trả về theo giá trị là khi bạn có một đối tượng được đặt tên, rất lớn (như trong kích thước ngăn xếp của nó). Điều này là do NRVO chưa được đảm bảo (kể từ c ++ 20) và thậm chí việc di chuyển đối tượng sẽ rất chậm. Đề xuất của tôi và đề xuất trong Nguyên tắc cốt lõi của Cpp là luôn ưu tiên các đối tượng trả về theo giá trị (nếu nhiều giá trị trả về, hãy sử dụng struct (hoặc tuple)), trong đó ngoại lệ duy nhất là khi đối tượng di chuyển đắt tiền. Trong trường hợp đó, hãy sử dụng tham số tham chiếu không phải const.
KHÔNG BAO GIỜ là một ý kiến hay khi trả về một tài nguyên phải được giải phóng thủ công từ một hàm trong c ++. Không bao giờ làm điều đó. Ít nhất hãy sử dụng std :: unique_ptr hoặc tạo cấu trúc không cục bộ hoặc cục bộ của riêng bạn bằng một trình hủy giải phóng tài nguyên của nó ( RAII ) và trả về một bản sao của nó. Sau đó, cũng sẽ là một ý tưởng hay để xác định hàm tạo di chuyển và toán tử gán di chuyển nếu tài nguyên không có ngữ nghĩa di chuyển của riêng nó (và xóa phương thức tạo / gán bản sao).