CHỈNH SỬA: RETRACTION
Kerrek đúng ở đây: Tôi đã nhầm lẫn rằng phương thức std::thread
khởi tạo và std::bind
được thiết kế bởi các giao diện giống hệt nhau. Tuy nhiên, việc chuyển đổi tự động các đối số từ reference_wrapper<A>
thành A&
chỉ được chỉ định std::bind
trong [func.bind.bind] / 10:
Giá trị của các đối số bị ràng buộc v1, v2, ..., vN
và kiểu tương ứng của chúng V1, V2, ..., VN
phụ thuộc vào kiểu TiD
bắt nguồn từ lệnh gọi đến bind
và cv -qualifier cv của trình bao bọc cuộc gọi g
như sau:
- nếu
TiD
là reference_wrapper<T>
, đối số là tid.get()
và kiểu của nó Vi
là T&
; - ...
Vì vậy, việc sử dụng cụ thể reference_wrapper<A>
này không được hỗ trợ bởi std::thread
, nhưng được hỗ trợ bởi std::bind
. Thực tế std::thread
hoạt động giống hệt std::bind
trong trường hợp này trong các trình biên dịch khác / cũ hơn là lỗi, không phải hành vi của bản phát hành GCC 4,8 dòng.
Tôi sẽ để lại câu trả lời không chính xác ở đây với lời giải thích này với hy vọng rằng những người khác sẽ không mắc phải sai lầm này trong tương lai.
Câu trả lời ngắn (nhưng KHÔNG ĐÚNG)
Đây rõ ràng là một lỗi trong thư viện tiêu chuẩn đi kèm với GCC 4.8. Mã được biên dịch chính xác bởi:
Câu trả lời dài (và cũng KHÔNG ĐÚNG):
Các tác động của hàm std::thread
tạo
template <class F, class ...Args>
explicit thread(F&& f, Args&&... args);
được trình bày chi tiết trong C ++ 11 30.3.1.2 [thread.thread.constr] / 4:
Chuỗi thực thi mới thực thi
INVOKE(DECAY_COPY(std::forward<F>(f)),
DECAY_COPY(std::forward<Args>(args))...)
với các lời gọi DECAY_COPY
được đánh giá trong luồng xây dựng.
DECAY_COPY
được mô tả trong 30.2.6 [thread.decaycopy] / 1:
Ở một số nơi trong Điều khoản này, phép toán DECAY_COPY(x)
được sử dụng. Tất cả các cách sử dụng như vậy có nghĩa là gọi hàm decay_copy(x)
và sử dụng kết quả, trong đó decay_copy
được định nghĩa như sau:
template <class T> typename decay<T>::type decay_copy(T&& v)
{ return std::forward<T>(v); }
Trong lời gọi trong OP, std::thread t1(&A::foo, std::ref(a), 100);
cả ba đối số đều là các giá trị DECAY_COPY
sẽ sao chép thành các đối tượng trong môi trường của luồng mới trước lời gọi, có tác dụng được mô tả trong 20.8.2 [func.require] / 1:
Xác định INVOKE(f, t1, t2, ..., tN)
như sau:
(t1.*f)(t2, ..., tN)
khi nào f
là một con trỏ đến một hàm thành viên của một lớp T
và t1
là một đối tượng của kiểu T
hoặc một tham chiếu đến một đối tượng của kiểu T
hoặc một tham chiếu đến một đối tượng của kiểu dẫn xuất từ T
;((*t1).*f)(t2, ..., tN)
khi nào f
là một con trỏ đến một hàm thành viên của một lớp T
và t1
không phải là một trong các kiểu được mô tả trong mục trước;- ...
Đối với mã trong OP, f
là một con trỏ đến hàm thành viên của lớp A
có giá trị &A::foo
, t1
là một giá trị reference_wrapper<A>
có tham chiếu được lưu trữ tham chiếu đến a
và t2
là một int
với giá trị 100
. Viên đạn thứ hai của 20.8.2 / 1 được áp dụng. Vì t1
là a reference_wrapper
, *t1
đánh giá tham chiếu được lưu trữ (trên 20.8.3.3/1) và lời gọi trong luồng mới là hiệu quả
(a.*&A::foo)(100);
Vì vậy, có, tiêu chuẩn mô tả hoạt động của OP chính xác như mong đợi.
CHỈNH SỬA: Thật kỳ lạ, GCC 4.8 biên dịch chính xác ví dụ tương tự :
class A {
public:
void foo(int n) { std::cout << n << std::endl; }
};
int main()
{
A a;
auto foo = std::bind(&A::foo, std::ref(a), 100);
foo();
}