2009-07-30 39 views
22

Tôi tự hỏi làm thế nào mọi người đang sử dụng C++ 0x lambdas, về mặt phong cách mã hóa. Câu hỏi thú vị nhất là cách triệt để khi viết danh sách chụp. Một mặt, ngôn ngữ cho phép liệt kê các biến được nắm bắt một cách rõ ràng, và bởi "rõ ràng là tốt hơn so với quy tắc ngầm", do đó sẽ có ý nghĩa để thực hiện một danh sách đầy đủ để nêu rõ nội dung. Ví dụ:C++ 0x lambdas mã hóa kiểu

int sum; 
std::for_each(xs.begin(), xs.end(), [&sum](int x) { sum += x }); 

Một lý do khác cho việc này là, vì tuổi thọ của người dân địa phương ref-chụp không thay đổi chỉ vì họ đang bị bắt (và do đó, một lambda có thể dễ dàng kết thúc tham khảo một địa phương mà đời từ lâu đã kết thúc), làm cho việc chụp rõ ràng giúp giảm các lỗi đó và theo dõi chúng.

Mặt khác, ngôn ngữ cũng cố tình cung cấp phím tắt để tự động chụp tất cả người dân địa phương được tham chiếu, vì vậy rõ ràng nó được dự định sẽ được sử dụng. Và người ta có thể tuyên bố rằng đối với một ví dụ như ở trên, điều rất rõ ràng những gì xảy ra ngay cả với tự động chụp, và tuổi thọ của lambda là nó sẽ không vượt quá phạm vi xung quanh, vì vậy không có lý do gì để không sử dụng nó:

int sum; 
std::for_each(xs.begin(), xs.end(), [&](int x) { sum += x }); 

rõ ràng điều này không phải là tất cả-hoặc-không có gì, nhưng phải có một số lý do để quyết định khi nào tự động chụp, và khi nào thì làm chụp một cách rõ ràng. Có suy nghĩ gì không?

Một câu hỏi khác trong cùng mạch là khi sử dụng tính năng chụp theo bản sao - [=] và khi nào sử dụng tính năng chụp theo tham chiếu - [&]. Capture-by-copy rõ ràng là an toàn hơn vì không có vấn đề suốt đời, vì vậy người ta có thể cho rằng nó nên được sử dụng theo mặc định bất cứ khi nào không cần phải thay đổi giá trị đã capture (hoặc xem những thay đổi được thực hiện từ nơi khác). tham chiếu không nên được coi là tối ưu hóa (có khả năng sớm) trong những trường hợp như vậy, chỉ được áp dụng khi nó tạo sự khác biệt rõ ràng. Mặt khác, capture-by-reference hầu như luôn luôn nhanh hơn (đặc biệt vì nó thường có thể được tối ưu hóa xuống một bản sao, nếu nó thực sự nhanh hơn, đối với các loại nhỏ và các chức năng mẫu không thể in được như hầu hết các thuật toán STL), và an toàn nếu lambda không bao giờ sống lâu hơn phạm vi của nó (cũng là trường hợp cho tất cả các thuật toán STL), vì vậy việc mặc định để capture-by-reference trong trường hợp này là một tối ưu hóa vô hại và không hại.

Suy nghĩ của bạn là gì?

+2

Tôi đặc biệt quan tâm đến phản hồi từ những người đã sử dụng lambdas trong mã sản xuất, và do đó có một số kinh nghiệm ủng hộ quan điểm của họ; nhưng tất cả các thông tin phản hồi được chào đón. –

+2

Lambdas không thực sự được triển khai trong nhiều trình biên dịch - tôi nghi ngờ bạn sẽ tìm thấy nhiều mã sản xuất cho đến khi G ++ hoặc MSVC++ hỗ trợ chúng ở ít nhất – bdonlan

+0

MS hỗ trợ chúng trong VC10, nhưng người dùng sẽ không sử dụng các trình biên dịch beta cho mã sản xuất . Tương tự như vậy, ngay cả đối với các tính năng có trong g + +, tôi nghi ngờ rằng toàn bộ rất nhiều mã sản xuất đang sử dụng -std = C++ 0x. –

Trả lời

2

Bản năng ban đầu của tôi là việc nắm bắt bằng giá trị cung cấp nhiều hay ít giống như các lớp bên trong vô danh của Java, là số lượng đã biết. Nhưng thay vì sử dụng thủ thuật mảng-of-size-1 khi bạn muốn phạm vi kèm theo có thể thay đổi, bạn có thể chụp bằng tham chiếu thay thế. Đó là trách nhiệm của bạn để hạn chế thời gian của lambda trong phạm vi của referand.

Trong thực tế, tôi đồng ý với bạn rằng việc chụp theo tham chiếu phải là mặc định khi xử lý các thuật toán, mà tôi mong đợi sẽ là phần lớn các công dụng. Một sử dụng phổ biến cho các lớp bên trong vô danh trong Java là người nghe. Có ít giao diện kiểu người nghe được nhìn thấy trong C++ để bắt đầu, vì vậy nó là một nhu cầu thấp hơn, nhưng vẫn còn ở đó. Nó có thể là tốt nhất để nghiêm túc dính vào nắm bắt bởi giá trị trong trường hợp đó, để tránh cơ hội cho lỗi. Liệu giá trị theo từng giá trị của shared_ptr có phải là thành ngữ lớn hay không?

Tuy nhiên, tôi chưa sử dụng lambdas, vì vậy tôi có thể đã bỏ lỡ điều gì đó rất lớn.

+0

Cần lưu ý rằng sự vắng mặt của mô hình người nghe trong thư viện chuẩn C++ chủ yếu là do kích thước nhỏ của nó (đáng chú ý nhất là không có giao diện người dùng). Hầu như mọi bộ công cụ giao diện người dùng đều có trình nghe/sự kiện dưới dạng nào đó và một số trong số chúng (Gtk--, IIRC) cho phép bạn đăng ký các đối tượng chức năng tùy ý làm người nghe chứ không chỉ các hàm hoặc phương thức (ví dụ: Qt). Điều này có thể sẽ phát triển phổ biến hơn với việc sử dụng 'std :: tr1 :: function' (đó là' std :: function' trong C++ 0x) như là một cách tiêu chuẩn để làm điều này. Vì vậy, vấn đề sẽ ở đó. –

+1

Về việc nắm bắt giá trị của 'shared_ptr' đối với trạng thái đã ghi có thể sống lâu hơn phạm vi (chẳng hạn như người nghe) - điểm tốt, và trên thực tế chúng tôi có một số điều đó trong mã của chúng tôi rồi. –

+0

@Pavel Minaev: bạn không cần chụp 'shared_ptr'. bạn chỉ có thể nắm bắt biến theo giá trị (do đó nó outlives phạm vi), và sau đó khai báo lambda 'mutable', nó sẽ cho phép nó biến đổi các biến bị bắt theo giá trị – newacct

6

Tôi chưa bao giờ nghe nói đến quy tắc "rõ ràng là tốt hơn quy tắc ngầm" và tôi không đồng ý với quy tắc đó. Có những trường hợp nó là sự thật, tất nhiên, nhưng cũng có rất nhiều trường hợp mà nó không phải là. Đó là lý do tại sao 0x đang thêm suy luận kiểu với từ khóa auto sau khi tất cả.(và tại sao các tham số mẫu chức năng đã được suy ra khi có thể) Có nhiều trường hợp tiềm ẩn thích hợp hơn.

tôi đã không thực sự sử dụng C++ lambdas chưa (trừ chọc xung quanh với các phiên bản beta VC10), nhưng tôi muốn đi với hầu hết sau thời gian

std::for_each(xs.begin(), xs.end(), [&](int x) { sum += x }); 

lập luận của tôi? Tại sao không làm điều đó? Nó tiện lợi. Nó hoạt động. Và nó dễ dàng hơn để duy trì. Tôi không phải cập nhật danh sách chụp khi tôi sửa đổi phần thân của lambda. Tại sao tôi phải rõ ràng về điều gì đó mà trình biên dịch biết rõ hơn tôi? Trình biên dịch có thể tìm ra danh sách chụp dựa trên những gì thực sự được sử dụng.

Để chụp theo tham chiếu so với giá trị? Tôi sẽ áp dụng các quy tắc tương tự như tôi làm cho các chức năng thông thường. Nếu bạn cần ngữ nghĩa tham chiếu, hãy nắm bắt bằng tham chiếu. Nếu bạn cần sao chép ngữ nghĩa, hãy làm điều đó. Nếu một trong hai sẽ làm, thích giá trị cho các loại nhỏ, và tham khảo nếu sao chép là tốn kém.

Nó không có vẻ khác với sự lựa chọn bạn phải thực hiện khi thiết kế một chức năng thông thường.

Tôi có lẽ nên đọc thông số kỹ thuật cho lambdas, nhưng không phải là lý do chính cho danh sách chụp rõ ràng để bạn có thể nắm bắt một số biến theo giá trị và các biến khác theo tham chiếu?

+0

Không có lý do rõ ràng cho nó, nhưng có, nó là cái gì đó chỉ có thể với một danh sách chụp. –

+2

Về "trình biên dịch biết rõ hơn tôi" - trình biên dịch không biết tuổi thọ của lambda. Nếu bạn đã gán một '[&]' lambda thành 'std :: function' trong phạm vi ngoài, nó sẽ cho phép bạn làm như vậy và sau đó rời khỏi phạm vi lambda - nhưng, tất nhiên, tất cả các tham chiếu trong lambda sẽ không hợp lệ khi bạn thực sự gọi nó là ... –

+1

"Rõ ràng là tốt hơn là ngầm" là từ The Zen của Python (http://www.python.org/dev/peps/pep-0020/), nhưng điều đó không thực sự cụ thể cho Python. –

0

Tôi có thể thấy quy tắc chuẩn mã hóa mới tại đây! ;)

Đây là một chút giả tạo nhưng chỉ để làm nổi bật một "lợi thế" để trở thành rõ ràng, hãy xem xét những điều sau đây:

void foo (std::vector<int> v, int x1) 
{ 
    int sum = 0; 
    std::for_each (v.begin() 
    , v.end() 
    , [&](int xl) { sum += x1; } 
} 

Bây giờ, tôi đã cố ý chọn tên người nghèo vv cho điều này, nhưng nó chỉ là để minh họa điểm. Nếu chúng ta sử dụng một danh sách chụp rõ ràng thì mã trên sẽ không biên dịch, nhưng hiện tại nó sẽ được biên dịch.

Trong một môi trường rất nghiêm ngặt (an toàn quan trọng), tôi có thể thấy một quy tắc như thế này là một phần của tiêu chuẩn mã hóa.

+2

Mặc dù những gì bạn nói là đúng, bạn có thể làm cho chính xác cùng một sai lầm nếu bạn đã viết một vòng lặp for, và đã làm xl = * iterator int; sum + = x1 ;. Không ai yêu cầu nắm bắt rõ ràng trong tình huống đó. Trong trường hợp cuộc đời của lambda bị giới hạn trong phạm vi địa phương, tôi nghĩ rằng việc nắm bắt tiềm ẩn không còn nguy hiểm hơn thực tế là các dấu ngoặc đơn giản thừa hưởng phạm vi tự động xung quanh. Đó là khi lambda là sẽ sống trên đó tôi nghĩ rằng bạn muốn được rõ ràng những gì bị bắt (và suy ra rằng những gì không bị bắt có thể chết một cách an toàn ở cuối phạm vi từ vựng). –

+1

Điều quan trọng là lỗi có thể tồn tại trong các cấu trúc khác. Đây là một lợi thế bổ sung của lambda, trong đó bạn có thể làm giảm khả năng hiển thị của tên hơn là có thể cho một "for loop". Có nhiều cách ẩn tên/hiển thị có thể dẫn đến các lỗi tinh vi. Một mục tiêu của tiêu chuẩn mã hóa là giảm các trường hợp có thể gây ra vấn đề và bạn sẽ tìm thấy một số quy tắc liên quan đến việc ẩn tên và giảm khả năng hiển thị tên trong các tiêu chuẩn như JSF ++, MISRA C/C++. Tôi có thể thấy điều này là khác. –

+0

Nhưng nó không thực sự làm giảm khả năng hiển thị so với functor tương đương, nó chỉ ngăn cản nó được mở rộng. Đó không phải là một lợi thế, đó là giảm thiểu. Vì vậy, vào thời điểm này tôi nghĩ rằng lambdas (rất ít) tồi tệ hơn các functors, trong đó nếu bạn nghĩ rằng mặc định chụp là xấu, bây giờ bạn phải cấm nó và cảnh sát lệnh cấm. Nhưng có, có lẽ tốt hơn so với lập trình có cấu trúc. –

0

Tôi sẽ đi với danh sách chụp rõ ràng khi thuận tiện, khi bạn muốn chụp nhiều biến sau đó (có thể bạn đang làm gì đó sai) và bạn có thể sử dụng danh sách chụp tất cả [&].

Vấn đề của tôi là danh sách chụp rõ ràng là lý tưởng và các biến thể tiềm ẩn nên tránh và chỉ ở đó để mọi người sẽ không phải loại bỏ các đoạn mã khi chúng thực sự cần thiết.