2013-05-26 48 views
6

Sao chụp-by-value giá trị const, nhưng bị bắt-by-reference đối tượng không được phép:Lambda: Tại sao giá trị được ghi lại bởi giá trị const, nhưng giá trị theo từng tham chiếu không?

int a; 

auto compile_error = [=]() 
{ 
    a = 1; 
} 
auto compiles_ok = [&]() 
{ 
    a = 1; 
} 

Đối với tôi này dường như vô lý nhưng nó dường như là tiêu chuẩn? Đặc biệt là sự sửa đổi không mong muốn của một giá trị bắt được có thể là một lỗi gây phiền nhiễu, nhưng rất có thể là hậu quả bị giới hạn trong phạm vi lambda, trong khi việc sửa đổi không mong muốn đối tượng bị bắt bằng tham chiếu sẽ dẫn đến các hiệu ứng nghiêm trọng hơn.

Vậy tại sao không chụp bằng tham chiếu const cho mỗi mặc định? Hoặc ít nhất hỗ trợ [const &] và [&]? Lý do cho thiết kế này là gì?

Vì cách giải quyết khác, bạn có thể phải sử dụng tham chiếu std :: cref được bao bọc bởi giá trị?

+0

Phần lớn điểm trong việc chụp tham chiếu đang thay đổi nó. Có * không * lý do để thay đổi một giá trị - nó là vô nghĩa. – Elazar

+3

Tài liệu tham khảo là không thay đổi và không bao giờ có thể thay đổi sau khi bị ràng buộc. Vì vậy, trong một cảm giác lambda biểu hiện phù hợp với phần còn lại của ngôn ngữ ở đây. Bạn có thể muốn tìm hiểu ý nghĩa của 'mutable' cho các biểu thức lambda. –

+0

@Elazar: Các giá trị tạm thời có thể thay đổi, nhưng vẫn như bạn đề xuất không có vấn đề gì để thay đổi giá trị, trong khi thay đổi một đối tượng tham chiếu thực sự tác động đến các phạm vi khác có thể không mong muốn (lỗi). – valoh

Trả lời

1

Tham chiếu được chụp cũng const. Hay đúng hơn, tham chiếu là luôn là hoàn toàn const - không có cú pháp nào trong ngôn ngữ cho phép bạn thay đổi nơi tham chiếu trỏ đến. a = 1; khi a là tham chiếu không thay đổi tham chiếu, nhưng thay đổi điều tham chiếu tham chiếu.

Khi bạn nói về "tham chiếu const", tôi nghĩ bạn đang bối rối. Bạn đang nói về "tham chiếu đến const int" (const int &). "Const" có đề cập đến điểm mà tham chiếu trỏ tới, không phải là tham chiếu. Nó tương tự với con trỏ: với "con trỏ đến const int" (const int *), con trỏ chính nó không phải là const - bạn có thể gán cho một biến loại này tất cả các bạn muốn. Một "con trỏ const" thực sẽ là int *const. Ở đây, bạn không thể gán cho một cái gì đó thuộc loại này; nhưng bạn có thể sửa đổi int nó trỏ đến. Do đó, "const" cho con trỏ hoặc tham chiếu tách biệt với "const" cho điều nó trỏ tới. Bạn cũng có thể có "const pointer to const int": const int *const.

+1

Tất nhiên tôi biết rằng tham chiếu luôn luôn tham chiếu luôn đến cùng một đối tượng, ý tôi là const tham chiếu như tham chiếu đến giá trị const (chủ đề gây nhầm lẫn cố định). Khi sử dụng lambdas trong bối cảnh đa luồng (hệ thống đánh cắp nhiệm vụ), việc viết không mong muốn dễ dàng dẫn đến các điều kiện chủng tộc. Imo với khái niệm chính xác const C++ có lợi thế hơn các ngôn ngữ khác (như C#), nhưng trong ngữ cảnh của lambdas nó có vẻ hơi lộn xộn một chút. Và tôi muốn biết lý do tại sao không có cách nào dễ dàng để có lambdas const nơi các đối tượng thay đổi được đánh dấu rõ ràng. – valoh

0

Logic của tôi nói như sau: lambdas chỉ là một đoạn mã, với các tham chiếu cần thiết tùy chọn. Trong trường hợp bạn cần thực sự sao chép một cái gì đó (thường xảy ra cho mục đích quản lý bộ nhớ, chẳng hạn như sao chép shared_ptr), bạn vẫn không thực sự muốn lambda có trạng thái riêng của nó. Đó là một tình huống khá bất thường.

Tôi nghĩ rằng chỉ có 2 lựa chọn sau đây cảm thấy "đúng"

  • Kèm theo một số mã sử dụng một số các biến địa phương, do đó bạn có thể "vượt qua nó xung quanh"
  • Giống như trên, chỉ thêm quản lý bộ nhớ , bởi vì có thể bạn muốn thực thi đoạn mã đó một cách không đồng bộ hoặc một cái gì đó và phạm vi tạo sẽ biến mất.

Nhưng khi một lambda là "có thể thay đổi", nghĩa là, nó chụp các giá trị mà không const, điều này có nghĩa rằng nó thực sự hỗ trợ của chính mình bang. Có nghĩa là, mỗi khi bạn gọi số lambda, nó có thể mang lại kết quả khác, không dựa trên đóng cửa thực tế, nhưng một lần nữa, trên trạng thái nội bộ nội bộ của chúng tôi. , xem xét rằng lambdas có nguồn gốc từ các ngôn ngữ chức năng.Tuy nhiên, C++, là C++, cung cấp cho bạn một cách để vượt qua giới hạn đó, bằng cách làm cho nó xấu xí hơn một chút, chỉ để đảm bảo rằng bạn nhận thức được thực tế rằng bạn đang làm điều gì đó lạ lùng.

Tôi hy vọng lý do này với bạn.

+0

Tôi hoàn toàn hiểu lý do tại sao chụp theo giá trị là const và hoàn toàn đồng ý rằng đây là một ý tưởng tốt để có nó theo mặc định. Nhưng những gì tôi vẫn không nhận được là sự thiếu hỗ trợ constness cho capture-by-reference, đặc biệt là điều này ngăn cản việc sử dụng lamba mạnh mẽ trong môi trường đa luồng -> non-const == có thể điều kiện chủng tộc tốt hơn nên chắc chắn chỉ là không const khi bạn thực sự có nghĩa là nó. – valoh

+0

Ồ. Tôi muốn nói đó là một phần của một trường hợp cạnh. IMO lambda giao dịch với các biến cục bộ giống như chúng. Nếu bạn thêm cú pháp đặc biệt cho mỗi trường hợp cạnh, bạn sẽ kết thúc với một thứ quái dị ... tốt, ít nhất * một * quái dị nhất, ít nhất.Như bạn có thể tưởng tượng, bạn vẫn có thể, có chức năng, có được những gì bạn muốn có ở đây, bằng cách chỉ đơn giản là răng cưa biến của bạn để một const-ref của nó, và sau đó chụp bí danh đó. –

+0

Có lẽ tôi đang mong đợi quá nhiều :-) Và thực sự tôi đoán tôi đã nói sai câu hỏi ban đầu của tôi rất hiểu lầm. Bây giờ tôi sẽ tranh luận những lý do tương tự để tạo ra giá trị theo từng giá trị là const áp dụng cho việc nắm bắt tham chiếu. Và để sửa đổi một đối tượng const bạn luôn có const cast làm cho nó thực sự rõ ràng những gì có được sửa đổi. Ít nhất hỗ trợ cho [const &]() {} không có vẻ quá nhiều. Ở dạng hiện tại, việc xử lý const của tham chiếu bắt giữ có vẻ hơi cẩu thả và không hỗ trợ việc sử dụng sạch sẽ và mạnh mẽ. – valoh

7

Giả sử bạn đang chụp con trỏ theo giá trị. Con trỏ chính nó là const, nhưng truy cập vào đối tượng mà nó trỏ đến không phải là.

int i = 0; 
int* p = &i; 
auto l = [=]{ ++*p; }; 
l(); 
std::cout << i << std::endl; // outputs 1 

lambda này tương đương với:

struct lambda { 
    int* p; 
    lambda(int* p_) : p(p_) {} 
    void operator()() const { ++*p; } 
}; 

Các const trên operator()() làm cho việc sử dụng của p tương đương với tuyên bố nó như là:

int* const p; 

điều tương tự sẽ xảy ra với một tham chiếu. Tham chiếu chính nó là "const" (trong dấu ngoặc kép bởi vì tham chiếu không thể được reseated), nhưng truy cập vào đối tượng nó đề cập đến là không.

Các vấn đề liên quan