Tại sao tôi không thể nắm bắt điều này bằng tham chiếu ('& this') trong lambda?


91

Tôi hiểu cách chính xác để nắm bắt this(để sửa đổi thuộc tính đối tượng) trong lambda như sau:

auto f = [this] () { /* ... */ };

Nhưng tôi tò mò về điểm đặc biệt sau đây mà tôi đã thấy:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

Điều kỳ lạ mà tôi bối rối (và muốn được giải đáp) là lý do tại sao các tác phẩm sau lại hoạt động:

auto f = [&] () { /* ... */ }; // capture everything by reference

Và tại sao tôi không thể nắm bắt rõ ràng thisbằng cách tham chiếu:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.

6
Tại sao bạn muốn ? Về những thứ mà tham chiếu đến một con trỏ có thể hữu ích: thiskhông thể thay đổi, nó không đủ lớn để tạo tham chiếu nhanh hơn ... và dù sao , nó không thực sự tồn tại , vì vậy nó có không có thời gian tồn tại thực, có nghĩa là mọi tham chiếu đến nó sẽ bị treo lơ lửng theo định nghĩa. thislà prvalue, không phải lvalue.
underscore_d

Câu trả lời:


111

Lý do [&this]không hoạt động là vì nó là một lỗi cú pháp. Mỗi tham số được phân tách bằng dấu phẩy trong lambda-introducercapture:

capture:
    identifier
    & identifier
    this

Bạn có thể thấy điều đó &thiskhông được phép về mặt cú pháp. Lý do nó không được phép là bởi vì bạn sẽ không bao giờ muốn nắm bắt thisbằng tham chiếu, vì nó là một con trỏ const nhỏ. Bạn sẽ chỉ muốn chuyển nó theo giá trị - vì vậy ngôn ngữ chỉ không hỗ trợ nắm bắt thisbằng cách tham chiếu.

Để nắm bắt thisrõ ràng, bạn có thể sử dụng [this]như lambda-introducer.

Đầu tiên capturecó thể là một capture-defaultđó là:

capture-default:
    &
    =

Điều này có nghĩa là nắm bắt tự động bất kỳ thứ gì tôi sử dụng, theo tham chiếu ( &) hoặc theo giá trị ( =) tương ứng - tuy nhiên cách xử lý thislà đặc biệt - trong cả hai trường hợp, nó được ghi lại theo giá trị vì những lý do đã đưa ra trước đó (ngay cả với việc nắm bắt mặc định &, thường có nghĩa là nắm bắt bằng cách tham khảo).

5.1.2.7/8:

Với mục đích tra cứu tên (3.4), xác định kiểu và giá trị của this(9.3.2) và chuyển đổi biểu thức id tham chiếu đến các thành viên lớp không tĩnh thành các biểu thức truy cập thành viên lớp bằng cách sử dụng (*this)(9.3.1), câu lệnh ghép [OF THE LAMBDA] được xem xét trong ngữ cảnh của biểu thức lambda.

Vì vậy, lambda hoạt động như thể nó là một phần của hàm thành viên bao quanh khi sử dụng tên thành viên (như trong ví dụ của bạn về việc sử dụng tên x), vì vậy nó sẽ tạo ra "tập quán ngầm" thisgiống như một hàm thành viên.

Nếu một lambda-capture bao gồm một capture mặc định &, thì các số nhận dạng trong lambda-capture sẽ không được đặt trước &. Nếu một lambda-capture có chứa mặc định là =capture, lambda-capture sẽ không chứa thisvà mỗi định danh mà nó chứa sẽ được đặt trước &. Một số nhận dạng hoặc thissẽ không xuất hiện nhiều hơn một lần trong quá trình chụp lambda.

Vì vậy, bạn có thể sử dụng [this], [&], [=]hoặc [&,this]như một lambda-introducerđể nắm bắt những thiscon trỏ theo giá trị.

Tuy nhiên [&this][=, this]được hình thành xấu. Trong trường hợp cuối cùng, gcc tha thứ cảnh báo [=,this]điều đó explicit by-copy capture of ‘this’ redundant with by-copy capture defaulthơn là lỗi.


3
@KonradRudolph: Điều gì sẽ xảy ra nếu bạn muốn nắm bắt một số thứ theo giá trị và những thứ khác bằng cách tham khảo? Hay muốn thật rõ ràng với những gì bạn chụp?
Xeo

2
@KonradRudolph: Đó là một tính năng an toàn. Bạn có thể vô tình chụp được những cái tên mà bạn không có ý định.
Andrew Tomazos

8
@KonradRudolph: Các cấu trúc cấp khối không sao chép một cách kỳ diệu con trỏ tới các đối tượng mà chúng sử dụng thành một kiểu ẩn danh vô hình mới sau đó có thể tồn tại trong phạm vi bao quanh - đơn giản bằng cách sử dụng tên đối tượng trong một biểu thức. Đánh bắt Lambda là một công việc nguy hiểm hơn rất nhiều.
Andrew Tomazos

5
@KonradRudolph Tôi sẽ nói "sử dụng [&]nếu bạn đang làm điều gì đó như tạo một khối để chuyển vào cấu trúc điều khiển", nhưng nắm bắt rõ ràng nếu bạn đang tạo ra một lambda sẽ được sử dụng cho các mục đích ít đơn giản hơn. [&]là một ý tưởng kinh khủng nếu lambda sẽ tồn tại lâu hơn phạm vi hiện tại. Tuy nhiên, nhiều cách sử dụng lambdas chỉ là cách để truyền các khối đến cấu trúc điều khiển và khối sẽ không tồn tại lâu hơn khối mà nó được tạo trong phạm vi.
Yakk - Adam Nevraumont

2
@Ruslan: Không, thislà một từ khóa, thiskhông phải là một định danh.
Andrew Tomazos

6

Vì tiêu chuẩn không có &thistrong danh sách Chụp ảnh:

N4713 8.4.5.2 Chụp:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. Đối với mục đích chụp lambda, một biểu thức có thể tham chiếu đến các thực thể cục bộ như sau:

    7.3 Biểu thức this có thể tham chiếu đến * this.

Vì vậy, đảm bảo tiêu chuẩn this*thishợp lệ, và &thiskhông hợp lệ. Ngoài ra, chụp thiscó nghĩa là chụp *this(là giá trị, chính đối tượng) bằng tham chiếu , chứ không phải là bắt thiscon trỏ theo giá trị !


*thischụp đối tượng theo giá trị
sp2danny
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.