Như @ JDługosz chỉ ra trong các bình luận, Herb đưa ra lời khuyên khác trong một cuộc nói chuyện khác (sau này?), Xem đại khái từ đây: https://youtu.be/xnqTKD8uD64?t=54m50s .
Lời khuyên của anh ta chỉ rút gọn khi chỉ sử dụng các tham số giá trị cho một hàm f
lấy cái gọi là đối số chìm, giả sử bạn sẽ di chuyển cấu trúc từ các đối số chìm này.
Cách tiếp cận chung này chỉ thêm chi phí chung của một hàm tạo di chuyển cho cả đối số lvalue và rvalue so với triển khai tối ưu của các f
đối số lvalue và rvalue tương ứng. Để xem tại sao lại như vậy, giả sử f
lấy một tham số giá trị, trong đó T
một số bản sao và di chuyển loại có thể xây dựng:
void f(T x) {
T y{std::move(x)};
}
Gọi f
với một đối số lvalue sẽ dẫn đến một hàm tạo sao chép được gọi để xây dựng x
và một hàm tạo di chuyển được gọi để xây dựng y
. Mặt khác, việc gọi f
với một đối số giá trị sẽ khiến một hàm tạo di chuyển được gọi để xây dựng x
và một hàm tạo di chuyển khác được gọi để xây dựng y
.
Nói chung, việc triển khai tối ưu f
cho các đối số lvalue như sau:
void f(const T& x) {
T y{x};
}
Trong trường hợp này, chỉ có một hàm tạo sao chép được gọi để xây dựng y
. Việc triển khai tối ưu f
cho các đối số giá trị là, nói chung, như sau:
void f(T&& x) {
T y{std::move(x)};
}
Trong trường hợp này, chỉ có một hàm tạo di chuyển được gọi để xây dựng y
.
Vì vậy, một sự thỏa hiệp hợp lý là lấy một tham số giá trị và có thêm một lệnh gọi hàm tạo di chuyển bổ sung cho các đối số giá trị hoặc giá trị liên quan đến việc triển khai tối ưu, đó cũng là lời khuyên được đưa ra trong bài nói chuyện của Herb.
Như @ JDługosz đã chỉ ra trong các bình luận, việc truyền theo giá trị chỉ có ý nghĩa đối với các hàm sẽ xây dựng một số đối tượng từ đối số chìm. Khi bạn có một chức năng f
sao chép đối số của nó, cách tiếp cận thông qua giá trị sẽ có nhiều chi phí hơn so với cách tiếp cận tham chiếu thông qua chung. Cách tiếp cận pass-by-value cho một hàm f
giữ một bản sao của tham số của nó sẽ có dạng:
void f(T x) {
T y{...};
...
y = std::move(x);
}
Trong trường hợp này, có một cấu trúc sao chép và gán di chuyển cho một đối số giá trị, và một chuyển nhượng xây dựng và di chuyển cho một đối số giá trị. Trường hợp tối ưu nhất cho một đối số lvalue là:
void f(const T& x) {
T y{...};
...
y = x;
}
Điều này chỉ rút gọn thành một nhiệm vụ, có khả năng rẻ hơn nhiều so với hàm tạo sao chép cộng với chuyển nhượng cần thiết cho cách tiếp cận giá trị truyền qua. Lý do cho điều này là việc gán có thể sử dụng lại bộ nhớ được phân bổ hiện có y
và do đó ngăn chặn (de) phân bổ, trong khi đó, hàm tạo sao chép thường sẽ phân bổ bộ nhớ.
Đối với một đối số giá trị, việc triển khai tối ưu nhất cho f
việc giữ lại một bản sao có dạng:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
Vì vậy, chỉ có một nhiệm vụ di chuyển trong trường hợp này. Việc chuyển một giá trị cho phiên bản f
lấy tham chiếu const chỉ tốn một nhiệm vụ thay vì chỉ định di chuyển. Vì vậy, tương đối mà nói, phiên bản f
lấy tham chiếu const trong trường hợp này là triển khai chung là thích hợp hơn.
Vì vậy, nói chung, để thực hiện tối ưu nhất, bạn sẽ cần quá tải hoặc thực hiện một số loại chuyển tiếp hoàn hảo như trong bài nói chuyện. Hạn chế là một vụ nổ tổ hợp về số lượng quá tải cần thiết, tùy thuộc vào số lượng tham số f
trong trường hợp bạn chọn quá tải trên danh mục giá trị của đối số. Chuyển tiếp hoàn hảo có nhược điểm f
trở thành chức năng mẫu, điều này ngăn việc biến nó thành ảo và dẫn đến mã phức tạp hơn đáng kể nếu bạn muốn làm cho đúng 100% (xem phần thảo luận để biết chi tiết chính).