Đầu tiên, "giới thiệu vòng loại cho * này" chỉ là một "tuyên bố tiếp thị". Các loại *this
không bao giờ thay đổi, xem dưới cùng của bài viết này. Đó là cách dễ dàng hơn để hiểu nó với từ ngữ này mặc dù.
Tiếp theo, đoạn mã sau chọn hàm được gọi dựa trên bộ định mức ref của "tham số đối tượng ẩn" của hàm † :
// t.cpp
#include <iostream>
struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};
int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}
Đầu ra:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Toàn bộ điều được thực hiện để cho phép bạn tận dụng thực tế khi đối tượng mà hàm được gọi là một giá trị (ví dụ tạm thời không được đặt tên). Lấy mã sau đây làm ví dụ khác:
struct test2{
std::unique_ptr<int[]> heavy_resource;
test2()
: heavy_resource(new int[500]) {}
operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];
return p;
}
operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};
Điều này có thể là một chút giả định, nhưng bạn nên có ý tưởng.
Lưu ý rằng bạn có thể kết hợp các vòng loại cv ( const
và volatile
) và vòng loại ref ( &
và &&
).
Lưu ý: Nhiều trích dẫn tiêu chuẩn và giải thích quá tải giải quyết sau đây!
Để hiểu cách thức hoạt động của điều này và tại sao câu trả lời của @Nicol Bolas ít nhất là sai một phần, chúng tôi phải khai thác tiêu chuẩn C ++ một chút (phần giải thích tại sao câu trả lời của @ Nicol sai ở phía dưới, nếu bạn là chỉ quan tâm đến điều đó).
Hàm nào sẽ được gọi được xác định bởi một quá trình gọi là giải quyết quá tải . Quá trình này khá phức tạp, vì vậy chúng tôi sẽ chỉ chạm vào bit quan trọng đối với chúng tôi.
Đầu tiên, điều quan trọng là phải xem cách giải quyết quá tải cho các chức năng thành viên:
§13.3.1 [over.match.funcs]
p2 Tập hợp các hàm ứng cử viên có thể chứa cả các hàm thành viên và không phải thành viên sẽ được giải quyết theo cùng một danh sách đối số. Vì vậy, danh sách tham số và tham số có thể so sánh được trong tập hợp không đồng nhất này, một hàm thành viên được coi là có một tham số phụ, được gọi là tham số đối tượng ẩn, đại diện cho đối tượng mà hàm thành viên được gọi . [...]
p3 Tương tự, khi thích hợp, bối cảnh có thể xây dựng một danh sách đối số có chứa một đối số ngụ ý để biểu thị đối tượng được vận hành trên đó.
Tại sao chúng ta thậm chí cần so sánh các chức năng thành viên và không thành viên? Toán tử quá tải, đó là lý do tại sao. Xem xét điều này:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Bạn chắc chắn muốn sau đây gọi chức năng miễn phí, phải không?
char const* s = "free foo!\n";
foo f;
f << s;
Đó là lý do tại sao các chức năng thành viên và không phải thành viên được bao gồm trong cái gọi là tập quá tải. Để làm cho độ phân giải ít phức tạp hơn, phần in đậm của trích dẫn tiêu chuẩn tồn tại. Ngoài ra, đây là bit quan trọng đối với chúng tôi (cùng một mệnh đề):
p4 Đối với các hàm thành viên không tĩnh, loại tham số đối tượng ẩn là
Tham chiếu lvalue cho cv Hướng dẫn X
cho các chức năng được khai báo mà không cần vòng loại hoặc với &
vòng loại
“Tài liệu tham khảo rvalue để cv X
” cho các chức năng khai báo với các &&
ref-vòng loại
Trường hợp X
chức năng là thành viên và cv là tiêu chuẩn cv trên khai báo hàm thành viên. [...]
p5 Trong quá trình phân giải quá tải [...] [t] anh ta ẩn tham số đối tượng [...] giữ lại danh tính của mình vì các chuyển đổi trên đối số tương ứng sẽ tuân theo các quy tắc bổ sung sau:
[...]
(Bit cuối cùng chỉ có nghĩa là bạn không thể gian lận độ phân giải quá tải dựa trên chuyển đổi ngầm định của đối tượng mà một hàm thành viên (hoặc toán tử) được gọi.)
Hãy lấy ví dụ đầu tiên ở đầu bài này. Sau khi chuyển đổi đã nói ở trên, bộ quá tải trông giống như thế này:
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
Sau đó, danh sách đối số, chứa đối số ngụ ý , được khớp với danh sách tham số của mọi hàm có trong tập quá tải. Trong trường hợp của chúng tôi, danh sách đối số sẽ chỉ chứa đối số đối tượng đó. Chúng ta hãy xem nó trông như thế nào:
// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set
Nếu, sau khi tất cả các quá tải trong tập hợp được kiểm tra, chỉ còn lại một, độ phân giải quá tải đã thành công và hàm được liên kết với quá tải được chuyển đổi đó được gọi. Điều tương tự cũng xảy ra với cuộc gọi thứ hai đến 'f':
// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set
Tuy nhiên, xin lưu ý rằng, nếu chúng tôi không cung cấp bất kỳ vòng loại giới thiệu nào (và như vậy không bị quá tải chức năng), thì điều đó f1
sẽ phù hợp với một giá trị (vẫn §13.3.1
):
p5 [...] Đối với các hàm thành viên không tĩnh được khai báo mà không có vòng loại giới thiệu , áp dụng quy tắc bổ sung:
- ngay cả khi tham số đối tượng ẩn không
const
đủ tiêu chuẩn, một giá trị có thể được liên kết với tham số miễn là trong tất cả các khía cạnh khác, đối số có thể được chuyển đổi thành loại tham số đối tượng ẩn.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Bây giờ, tại sao câu trả lời của @ Nicol ít nhất là sai. Anh ta nói:
Lưu ý rằng tuyên bố này thay đổi loại *this
.
Đó là sai lầm, *this
là luôn luôn một giá trị trái:
§5.3.1 [expr.unary.op] p1
Các unary *
Thực hiện điều hành gián tiếp : các biểu hiện mà nó được áp dụng sẽ là một con trỏ đến một loại đối tượng, hoặc một con trỏ đến một loại chức năng và kết quả là một giá trị trái đề cập đến đối tượng hoặc chức năng mà các điểm biểu.
§9.3.2 [class.this] p1
Trong phần thân của hàm thành viên không tĩnh (9.3), từ khóa this
là biểu thức prvalue có giá trị là địa chỉ của đối tượng mà hàm được gọi. Loại this
trong một chức năng thành viên của một lớp X
là X*
. [...]