Cho phép chơi với NRVO, RVO và sao chép elision!
Dưới đây là một loại:
#include <iostream>
struct Verbose {
Verbose(Verbose const&){ std::cout << "copy ctor\n"; }
Verbose(Verbose &&){ std::cout << "move ctor\n"; }
Verbose& operator=(Verbose const&){ std::cout << "copy asgn\n"; }
Verbose& operator=(Verbose &&){ std::cout << "move asgn\n"; }
};
đó là khá dài dòng.
Đây là một chức năng:
Verbose simple() { return {}; }
rằng là khá đơn giản, và sử dụng xây dựng trực tiếp của giá trị trả về của nó. Nếu Verbose
thiếu một hàm tạo bản sao hoặc di chuyển, hàm trên sẽ hoạt động!
Đây là một chức năng sử dụng RVO:
Verbose simple_RVO() { return Verbose(); }
ở đây vô danh Verbose()
đối tượng tạm thời đang được nói để sao chép chính nó vào giá trị trả về. RVO có nghĩa là trình biên dịch có thể bỏ qua bản sao đó và trực tiếp xây dựng Verbose()
vào giá trị trả về, nếu và chỉ khi có một hàm tạo bản sao hoặc di chuyển. Các nhà xây dựng bản sao hoặc di chuyển không được gọi, mà là elided.
Đây là một chức năng sử dụng NRVO:
Verbose simple_NRVO() {
Verbose retval;
return retval;
}
Đối NRVO xảy ra, tất cả các con đường phải trả lại đối tượng chính xác giống nhau, và bạn không thể lén lút về nó (nếu bạn cast giá trị trở lại một tham chiếu, sau đó trả về tham chiếu đó, sẽ chặn NRVO). Trong trường hợp này, trình biên dịch làm gì là xây dựng đối tượng được đặt tên retval
trực tiếp vào vị trí giá trị trả về. Tương tự như RVO, một nhà xây dựng bản sao hoặc di chuyển phải tồn tại, nhưng không được gọi.
Đây là một chức năng mà không sử dụng NRVO:
Verbose simple_no_NRVO(bool b) {
Verbose retval1;
Verbose retval2;
if (b)
return retval1;
else
return retval2;
}
như có hai tên đối tượng có thể nó có thể trở lại, nó không thể xây dựng cả trong số họ ở vị trí giá trị trả về, vì vậy nó phải làm một bản sao thực tế. Trong C++ 11, đối tượng được trả về sẽ được ẩn hoàn toàn move
d thay vì sao chép, vì nó là một biến cục bộ được trả về từ một hàm trong câu lệnh trả về đơn giản. Vì vậy, có ít nhất đó.
Cuối cùng, có bản sự bỏ bớt ở đầu kia:
Verbose v = simple(); // or simple_RVO, or simple_NRVO, or...
Khi bạn gọi một hàm, bạn cung cấp cho nó với các đối số của nó, và bạn thông báo cho nó, nơi nó nên đặt giá trị trả về của nó. Người gọi có trách nhiệm làm sạch giá trị trả lại và phân bổ bộ nhớ (trên ngăn xếp) cho nó.
Giao tiếp này được thực hiện theo cách nào đó thông qua quy ước gọi điện, thường ngầm (nghĩa là thông qua con trỏ ngăn xếp).
Dưới nhiều quy ước gọi điện, vị trí mà giá trị trả về có thể được lưu trữ có thể sẽ được sử dụng làm biến cục bộ.
Nói chung, nếu bạn có một biến có dạng:
Verbose v = Verbose();
bản sao ngụ ý có thể được elided - Verbose()
được xây dựng trực tiếp trong v
, chứ không phải là tạm thời được tạo ra sau đó sao chép vào v
.Trong cùng một cách, giá trị trả về của simple
(hoặc simple_NRVO
hoặc bất kỳ thứ gì) có thể được ưu tiên nếu mô hình thời gian chạy của trình biên dịch hỗ trợ nó (và nó thường làm).
Về cơ bản, trang web gọi có thể yêu cầu simple_*
đặt giá trị trả về ở một vị trí cụ thể và chỉ cần xử lý điểm đó làm biến cục bộ v
.
Lưu ý rằng NRVO và RVO và di chuyển ngầm đều được thực hiện trong hàm và người gọi không cần biết gì về nó.
Tương tự như vậy, eliding tại trang web gọi là tất cả được thực hiện bên ngoài chức năng và nếu quy ước gọi hỗ trợ nó, bạn không cần bất kỳ sự hỗ trợ nào từ phần thân của hàm.
Điều này không nhất thiết phải đúng trong mọi quy ước gọi điện và mô hình thời gian chạy, vì vậy tiêu chuẩn C++ làm cho các tối ưu hóa này trở thành tùy chọn.
Mọi trình biên dịch đều có thể chọn để thực hiện theo bất kỳ cách nào họ muốn và nếu vấn đề của họ trong trường hợp bạn mô tả, họ có thể chọn luôn sử dụng RVO. Khi bạn đang intrested trong cách trình biên dịch khác nhau làm nó dưới mui xe, tôi đề nghị bạn đọc mã assembler tạo ra. Sử dụng trình khám phá gcc để dễ dàng truy cập vào lắp ráp được tạo bằng clang/gcc. – PlasmaHH
@PlasmaHH Nhưng RVO không phải lúc nào cũng có thể. –
Điều đó không có nghĩa là quy ước cuộc gọi sẽ thay đổi khi nó được hoặc không được sử dụng. Nhìn vào một số người lắp ráp cách họ làm điều đó. – PlasmaHH