Ưu điểm của std :: for_each qua vòng lặp


166

Có bất kỳ lợi thế của std::for_eachhơn forvòng lặp? Đối với tôi, std::for_eachdường như chỉ cản trở khả năng đọc mã. Tại sao sau đó một số tiêu chuẩn mã hóa khuyến nghị sử dụng nó?


10
std::for_eachkhi được sử dụng với boost.lambdahoặc boost.bindthường có thể cải thiện khả năng đọc

Các câu hỏi và câu trả lời được chấp nhận là từ năm 2010. Đối với một câu trả lời uptodate hơn (từ năm 2018), xem tại đây: fluentcpp.com/2018/03/30/is-stdfor_each-obsolete
Erel Segal-Halevi

Câu trả lời:


181

Điều tuyệt vời với C ++ 11 (trước đây gọi là C ++ 0x), là cuộc tranh luận mệt mỏi này sẽ được giải quyết.

Ý tôi là, không ai trong tâm trí của họ, người muốn lặp lại toàn bộ bộ sưu tập, vẫn sẽ sử dụng cái này

for(auto it = collection.begin(); it != collection.end() ; ++it)
{
   foo(*it);
}

Hoặc cái này

for_each(collection.begin(), collection.end(), [](Element& e)
{
   foo(e);
});

khi cú pháp vòng lặp dựa trên phạm vifor có sẵn:

for(Element& e : collection)
{
   foo(e);
}

Loại cú pháp này đã có sẵn trong Java và C # một thời gian rồi và thực sự có nhiều foreachvòng lặp hơn các forvòng lặp cổ điển trong mỗi mã Java hoặc C # gần đây tôi thấy.


12
Trên thực tế, một vòng lặp foreach với scoop đã có sẵn trong một thời gian dài để tăng tốc và tôi vẫn muốn lặp lại với for_each và một hàm lambda.
Viktor Sehr

19
Giả định về việc muốn toàn bộ phạm vi container không phải là một phần của câu hỏi, vì vậy đây chỉ là một câu trả lời một phần.
Adrian McCarthy

6
Lưu ý rằng việc lặp qua một phần tử có lẽ không phải là điều duy nhất bạn muốn làm, vì vậy có thể nên sử dụng for_each để bạn tìm hiểu về find / phân vùng / copy_Vplace_if và các phần tử khác, đó thực sự là rất nhiều cho các vòng lặp làm
Macke

10
Range-for là tốt, ngoại trừ khi bạn thực sự cần iterator (thì không có cách nào để truy cập nó).
Damon

5
Tôi sẽ không sử dụng ngay cả Element & ekhi auto & e(hoặc auto const &e) trông tốt hơn. Tôi sẽ sử dụng Element const e(không có tham chiếu) khi tôi muốn chuyển đổi ngầm định, giả sử khi nguồn là tập hợp các loại khác nhau và tôi muốn chúng chuyển đổi thành Element.
Nawaz

53

Dưới đây là một số lý do:

  1. Nó dường như cản trở khả năng đọc chỉ vì bạn không quen với nó và / hoặc không sử dụng đúng công cụ xung quanh nó để làm cho nó thực sự dễ dàng. (xem boost :: phạm vi và boost :: bind / boost :: lambda để được trợ giúp. Nhiều trong số này sẽ đi vào C ++ 0x và làm cho for_each và các chức năng liên quan trở nên hữu ích hơn.)

  2. Nó cho phép bạn viết một thuật toán trên đầu trang for_each hoạt động với bất kỳ trình vòng lặp nào.

  3. Nó làm giảm cơ hội lỗi đánh máy ngu ngốc.

  4. Nó cũng mở tâm trí của bạn với phần còn lại của STL-thuật toán, như find_if, sort, replace, vv và chúng sẽ không trông rất lạ nữa. Đây có thể là một chiến thắng rất lớn.

Cập nhật 1:

Quan trọng nhất, nó giúp bạn vượt ra ngoài for_eachso với các vòng lặp như tất cả những gì có, và xem xét các STL-alog khác, như find / sort / phân vùng / copy_Vplace_if, thực thi song song .. hoặc bất cứ điều gì.

Rất nhiều quá trình xử lý có thể được viết rất chính xác bằng cách sử dụng "phần còn lại" của anh chị em của for_each, nhưng nếu tất cả những gì bạn làm là viết một vòng lặp với logic bên trong khác nhau, thì bạn sẽ không bao giờ học cách sử dụng chúng, và bạn sẽ cuối cùng phát minh ra bánh xe hơn và hơn.

Và (for_each kiểu sắp có sẵn):

for_each(monsters, boost::mem_fn(&Monster::think));

Hoặc với C ++ x11 lambdas:

for_each(monsters, [](Monster& m) { m.think(); });

IMO dễ đọc hơn:

for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
    i->think();
} 

Ngoài ra điều này (hoặc với lambdas, xem những người khác):

for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));

Là ngắn gọn hơn:

for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
    my_monkey->eat(*i);
} 

Đặc biệt là nếu bạn có một số chức năng để gọi theo thứ tự ... nhưng có lẽ đó chỉ là tôi. ;)

Cập nhật 2 : Tôi đã viết các trình bao bọc một lớp của riêng mình các stl-algos hoạt động với các phạm vi thay vì các cặp trình vòng lặp. boost :: Range_ex, một khi được phát hành, sẽ bao gồm điều đó và có lẽ nó cũng sẽ có trong C ++ 0x?


+1, một số hàm hoặc các kiểu lồng nhau: outer_class::inner_class::iteratorhoặc chúng là các đối số mẫu: typename std::vector<T>::iterator... chính cấu trúc đó có thể tự chạy trong một cấu trúc nhiều dòng
David Rodríguez - dribeas

4
(btw: for_eachtrong ví dụ thứ hai là không chính xác (nên làfor_each( bananas.begin(), bananas.end(),...
David Rodríguez - dribeas

Tôi đã viết các hàm bao sử dụng phạm vi thay vì hai vòng lặp. Chúng sẽ có sau (xem phạm vi_ex) nhưng mọi người nên có chúng bằng mọi cách. (Đã thêm cập nhật về điều đó.)
Macke

1
Hỗ trợ xử lý song song là không. 1 lý do ở đây. Chúng ta có thể thêm triển khai để sử dụng cuda / gpu cho tính toán song song không đồng nhất.
shuva

24

for_eachlà chung chung hơn. Bạn có thể sử dụng nó để lặp lại trên bất kỳ loại container nào (bằng cách chuyển qua các vòng lặp bắt đầu / kết thúc). Bạn có khả năng có thể trao đổi các container bên dưới một hàm sử dụng for_eachmà không phải cập nhật mã lặp. Bạn cần xem xét rằng có những container khác trên thế giới hơn std::vectorvà các mảng C cũ đơn giản để thấy những lợi thế của for_each.

Nhược điểm chính của for_eachnó là cần một functor, vì vậy cú pháp rất khó hiểu. Điều này được cố định trong C ++ 11 (trước đây là C ++ 0x) với sự ra đời của lambdas:

std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
    i+= 10;
});

Điều này sẽ không có vẻ kỳ lạ với bạn trong 3 năm.


3
+1. Quảng cáo khi for_each mất một container / phạm vi thay vì hai lần lặp, nó sẽ tuyệt vời hơn bao giờ hết.
Macke

2
@Marcus: đó sẽ là cấu trúc tầm xa và cú pháp sẽ không tự đọc 'for_each': for ( int v : int_vector ) {(ngay cả khi nó có thể được mô phỏng ngày hôm nay với BOOST_FOREACH)
David Rodríguez - dribeas

@David: Tôi đang đề cập đến việc bổ sung chung các hàm dựa trên phạm vi (vì vậy bạn có thể sử dụng các phạm vi với tất cả các hàm for_each, copy, remove_if, v.v.),
Macke

1
Tại sao nó không thể viết : std::for_each(container, [](int& i){ ... });. Ý tôi là tại sao người ta buộc phải viết container hai lần?
Giorgio

1
@freitass: Viết container một lần như trong nhận xét trước đây của tôi có thể mặc định sử dụng trình lặp kết thúc bắt đầu mà không gọi chúng một cách rõ ràng. Hầu hết ngôn ngữ cung cấp các hàm bậc cao hơn trên các bộ sưu tập (Ruby, Scala, ...) viết một cái gì đó giống như container.each { ... }không đề cập đến các trình lặp bắt đầu và kết thúc. Tôi thấy nó hơi dư thừa rằng tôi phải chỉ định trình lặp kết thúc mọi lúc.
Giorgio

17

Cá nhân, bất cứ lúc nào tôi cần sử dụng std::for_each(viết hàm functor / boost::lambdas phức tạp cho mục đích đặc biệt ), tôi tìm BOOST_FOREACHvà dựa trên phạm vi của C ++ 0x để rõ ràng hơn:

BOOST_FOREACH(Monster* m, monsters) {
     if (m->has_plan()) 
         m->act();
}

đấu với

std::for_each(monsters.begin(), monsters.end(), 
  if_then(bind(&Monster::has_plan, _1), 
    bind(&Monster::act, _1)));

11

Rất chủ quan, một số người sẽ nói rằng việc sử dụng for_each sẽ làm cho mã dễ đọc hơn , vì nó cho phép xử lý các bộ sưu tập khác nhau với cùng một quy ước. for_eachItslef được thực hiện như một vòng lặp

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function f)
  {
    for ( ; first!=last; ++first ) f(*first);
    return f;
  }

Vì vậy, tùy thuộc vào bạn để chọn những gì phù hợp với bạn.


11

Giống như nhiều hàm thuật toán, một phản ứng ban đầu là nghĩ rằng việc sử dụng foreach không thể đọc được hơn là một vòng lặp. Đó là một chủ đề của nhiều cuộc chiến lửa.

Khi bạn đã quen với thành ngữ bạn có thể thấy nó hữu ích. Một lợi thế rõ ràng là nó buộc bộ mã hóa tách nội dung bên trong của vòng lặp khỏi chức năng lặp thực tế. (OK, tôi nghĩ đó là một lợi thế. Người khác nói rằng bạn chỉ đang cắt mã mà không có lợi ích thực sự).

Một lợi thế khác là khi tôi thấy foreach, tôi biết rằng mọi mục sẽ được xử lý hoặc một ngoại lệ sẽ bị ném.

Một vòng lặp for cho phép một số tùy chọn để kết thúc vòng lặp. Bạn có thể để vòng lặp chạy toàn bộ khóa học của mình hoặc bạn có thể sử dụng từ khóa break để nhảy ra khỏi vòng lặp hoặc sử dụng từ khóa return để thoát khỏi toàn bộ vòng lặp chức năng. Ngược lại, foreach không cho phép các tùy chọn này và điều này làm cho nó dễ đọc hơn. Bạn chỉ có thể lướt qua tên hàm và bạn biết bản chất đầy đủ của phép lặp.

Dưới đây là một ví dụ về một bối rối cho vòng lặp:

for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
   /////////////////////////////////////////////////////////////////////
   // Imagine a page of code here by programmers who don't refactor
   ///////////////////////////////////////////////////////////////////////
   if(widget->Cost < calculatedAmountSofar)
   {
        break;
   }
   ////////////////////////////////////////////////////////////////////////
   // And then some more code added by a stressed out juniour developer
   // *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
   /////////////////////////////////////////////////////////////////////////
   for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
   {
      if(ip->IsBroken())
      {
         return false;
      }
   }
}

1
Bạn đưa ra một quan điểm tốt, nhưng ví dụ động lực của bạn không hoàn toàn công bằng. Khi bạn sử dụng std::for_each()trong tiêu chuẩn cũ (thời điểm của bài đăng này), bạn phải sử dụng một functor có tên, điều này khuyến khích khả năng đọc như bạn nói và cấm thoát ra khỏi vòng lặp sớm. Nhưng sau đó, forvòng lặp tương đương không có gì ngoài một lệnh gọi hàm, và điều đó cũng cấm việc thoát ra sớm. Nhưng khác hơn là tôi nghĩ rằng bạn đã đưa ra một điểm tuyệt vời khi nói rằng việc std::for_each() thực thi đi qua toàn bộ phạm vi.
wilmustell

10

Bạn hầu như đúng: hầu hết thời gian, std::for_eachlà một mất mát ròng. Tôi sẽ đi xa để so sánh for_eachvới goto. gotocung cấp điều khiển luồng linh hoạt nhất có thể - bạn có thể sử dụng nó để thực hiện hầu như bất kỳ cấu trúc điều khiển nào khác mà bạn có thể tưởng tượng. Tuy nhiên, tính linh hoạt đó có nghĩa là việc nhìn thấy một gotosự cô lập cho bạn hầu như không có gì về những gì nó dự định làm trong tình huống này. Kết quả là, hầu như không ai trong tâm trí của họ sử dụng gotongoại trừ như là phương sách cuối cùng.

Trong số các thuật toán tiêu chuẩn, for_eachcó nhiều cách giống nhau - nó có thể được sử dụng để thực hiện hầu như mọi thứ, điều đó có nghĩa là việc nhìn thấy for_eachcho bạn hầu như không có gì về những gì nó được sử dụng cho tình huống này. Thật không may, thái độ của mọi người đối với for_eachviệc thái độ của họ đối với gotonăm 1970 như thế nào - một vài người đã bắt gặp thực tế rằng nó chỉ nên được sử dụng như là phương sách cuối cùng, nhưng nhiều người vẫn coi đó là thuật toán chính, và hiếm khi sử dụng bất kỳ khác. Phần lớn thời gian, thậm chí một cái liếc nhanh sẽ tiết lộ rằng một trong những lựa chọn thay thế là vượt trội đáng kể.

Ví dụ, tôi khá chắc chắn rằng tôi đã quên mất số lần tôi thấy mọi người viết mã để in ra nội dung của một bộ sưu tập bằng cách sử dụng for_each. Dựa trên các bài đăng tôi đã xem, đây cũng có thể là cách sử dụng phổ biến nhất for_each. Họ kết thúc với một cái gì đó như:

class XXX { 
// ...
public:
     std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};

Và bài viết của họ được hỏi về những gì kết hợp bind1st, mem_funvv Họ cần phải làm một cái gì đó như:

std::vector<XXX> coll;

std::for_each(coll.begin(), coll.end(), XXX::print);

làm việc và in ra các yếu tố của coll. Nếu nó thực sự hoạt động chính xác như tôi đã viết nó ở đó, thì nó sẽ tầm thường, nhưng nó không - và đến lúc bạn làm cho nó hoạt động, thật khó để tìm thấy một vài đoạn mã liên quan đến những gì đang diễn ra giữa các mảnh giữ nó lại với nhau.

May mắn thay, có một cách tốt hơn nhiều. Thêm quá tải bộ lọc luồng bình thường cho XXX:

std::ostream &operator<<(std::ostream *os, XXX const &x) { 
   return x.print(os);
}

và sử dụng std::copy:

std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));

Điều đó không làm việc - và mất hầu như không làm việc ở tất cả để tìm ra rằng nó in nội dung của collđể std::cout.


+1, mặc dù có một sai lầm. Trong ví dụ đầu tiên nó phải được boost::mem_fn(&XXX::print)hơnXXX::print
missingfaktor

Đó là lý do tại sao tôi nói rằng ví dụ đó sẽ không hoạt động và họ đang đăng yêu cầu trợ giúp để nó hoạt động (ồ, và bạn cũng cần phải ràng buộc std::coutnhư là đối số của nó để nó hoạt động).
Jerry Coffin

1
Mặc dù nó thường là một câu trả lời hay, câu hỏi không phải là về giá trị của for_each hay giá trị của nó so với các thuật toán tiêu chuẩn khác, mà là về giá trị của nó so với vòng lặp for. Bạn có thể nghĩ về nó trong trường hợp không áp dụng thuật toán tiêu chuẩn khác. Sau đó, bạn sẽ sử dụng một for_each hoặc một vòng lặp for? Hãy suy nghĩ về nó và bất cứ điều gì bạn nghĩ ra, đó nên là câu trả lời của bạn.
Christian Rau

@ChristianRau: Luôn có một ranh giới tốt giữa việc trả lời chính xác câu hỏi và cố gắng cung cấp thông tin hữu ích. Câu trả lời trực tiếp cho chính xác những câu hỏi mà anh ấy hỏi sẽ là "Có lẽ là không. Ai biết?", Nhưng sẽ quá vô dụng để đáng để bận tâm. Đồng thời đi quá xa (ví dụ, khuyến nghị Haskell thay vì bất kỳ điều nào ở trên) cũng không có khả năng được sử dụng nhiều.
Jerry Coffin

3
@ChristianRau: Làm thế nào để bạn nghĩ rằng "... hầu hết thời gian, std :: for_each là một khoản lỗ ròng" không giải quyết được câu hỏi liệu std :: for_each có mang lại lợi thế không?
Jerry Coffin

8

Lợi thế của chức năng viết để nuôi ong dễ đọc hơn, có thể không hiển thị khi for(...)for_each(... ).

Nếu bạn sử dụng tất cả các thuật toán trong function.h, thay vì sử dụng các vòng lặp for, mã sẽ dễ đọc hơn rất nhiều;

iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);

nhiều hơn dễ đọc hơn;

Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (*it > *longest_tree) {
     longest_tree = it;
   }
}

Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (it->type() == LEAF_TREE) {
     leaf_tree  = it;
     break;
   }
}

for (Forest::const_iterator it = forest.begin(), jt = firewood.begin(); 
     it != forest.end(); 
     it++, jt++) {
          *jt = boost::transformtowood(*it);
    }

for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
    std::makeplywood(*it);
}

Và đó là những gì tôi nghĩ là rất hay, hãy khái quát các vòng lặp for thành một hàm dòng =)


6

Easy: for_eachrất hữu ích khi bạn đã có một hàm để xử lý mọi mục mảng, vì vậy bạn không phải viết lambda. Chắc chắn, cái này

for_each(a.begin(), a.end(), a_item_handler);

tốt hơn

for(auto& item: a) {
    a_item_handler(a);
}

Ngoài ra, forvòng lặp ranged chỉ lặp đi lặp lại trên toàn bộ container từ đầu đến cuối, trong khi for_eachlinh hoạt hơn.


4

Các for_each vòng lặp có nghĩa là để che giấu sự lặp (chi tiết về cách thức một vòng lặp được thực hiện) từ mã người dùng và xác định ngữ nghĩa rõ ràng về hoạt động: mỗi phần tử sẽ được lặp đúng một lần.

Vấn đề với khả năng đọc trong tiêu chuẩn hiện tại là nó yêu cầu functor làm đối số cuối cùng thay vì một khối mã, vì vậy trong nhiều trường hợp, bạn phải viết loại functor cụ thể cho nó. Điều đó biến thành mã ít đọc hơn vì các đối tượng functor không thể được định nghĩa tại chỗ (các lớp cục bộ được xác định trong một hàm không thể được sử dụng làm đối số khuôn mẫu) và việc thực hiện vòng lặp phải được di chuyển khỏi vòng lặp thực.

struct myfunctor {
   void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), myfunctor() );
   // more code
}

Lưu ý rằng nếu bạn muốn thực hiện một thao tác cụ thể trên từng đối tượng, bạn có thể sử dụng std::mem_fnhoặc boost::bind( std::bindtrong tiêu chuẩn tiếp theo) hoặc boost::lambda(lambdas trong tiêu chuẩn tiếp theo) để làm cho nó đơn giản hơn:

void function( int value );
void apply( std::vector<X> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
   // code
}

Không dễ đọc và nhỏ gọn hơn phiên bản cuộn tay nếu bạn có chức năng / phương thức để gọi tại chỗ. Việc thực hiện có thể cung cấp các triển khai khác củafor_each vòng lặp (nghĩ xử lý song song).

Tiêu chuẩn sắp tới xử lý một số thiếu sót theo các cách khác nhau, nó sẽ cho phép các lớp được định nghĩa cục bộ làm đối số cho các mẫu:

void apply( std::vector<int> const & v ) {
   // code
   struct myfunctor {
      void operator()( int ) { code }
   };
   std::for_each( v.begin(), v.end(), myfunctor() );
   // code
}

Cải thiện địa phương của mã: khi bạn duyệt, bạn thấy những gì nó đang làm ngay tại đó. Như một vấn đề thực tế, bạn thậm chí không cần sử dụng cú pháp lớp để định nghĩa functor, nhưng sử dụng lambda ngay tại đó:

void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), 
      []( int ) { // code } );
   // code
}

Ngay cả trong trường hợp for_eachsẽ có một cấu trúc cụ thể sẽ làm cho nó tự nhiên hơn:

void apply( std::vector<int> const & v ) {
   // code
   for ( int i : v ) {
      // code
   }
   // code
}

Tôi có xu hướng trộn các for_eachcấu trúc với các vòng cuộn tay. Khi chỉ có một cuộc gọi đến một chức năng hoặc phương thức hiện có là những gì tôi cần ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )) tôi đi đến for_eachcấu trúc lấy đi từ mã rất nhiều công cụ lặp đĩa nồi hơi. Khi tôi cần một cái gì đó phức tạp hơn và tôi không thể thực hiện một functor chỉ một vài dòng trên mức sử dụng thực tế, tôi cuộn vòng lặp của riêng mình (giữ cho hoạt động tại chỗ). Trong các phần không quan trọng của mã, tôi có thể đi với BOOST_FOREACH (một đồng nghiệp đã đưa tôi vào đó)


3

Bên cạnh khả năng đọc và hiệu suất, một khía cạnh thường bị bỏ qua là tính nhất quán. Có nhiều cách để thực hiện vòng lặp for (hoặc while) trên các vòng lặp, từ:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
    do_something(*iter);
}

đến:

C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
    do_something(*iter);
    ++iter;
}

với nhiều ví dụ ở giữa mức độ hiệu quả và tiềm năng lỗi khác nhau.

Tuy nhiên, sử dụng for_each, thực thi tính nhất quán bằng cách trừu tượng hóa vòng lặp:

for_each(c.begin(), c.end(), do_something);

Điều duy nhất bạn phải lo lắng bây giờ là: bạn có triển khai thân vòng lặp như hàm, hàm functor hoặc lambda bằng các tính năng Boost hoặc C ++ 0x không? Cá nhân, tôi khá lo lắng về điều đó hơn là cách thực hiện hoặc đọc một vòng lặp for / while ngẫu nhiên.


3

Tôi đã từng không thích std::for_eachvà nghĩ rằng không có lambda, nó đã hoàn toàn sai. Tuy nhiên tôi đã thay đổi suy nghĩ của mình một thời gian trước đây và bây giờ tôi thực sự yêu thích nó. Và tôi nghĩ rằng nó thậm chí còn cải thiện khả năng đọc và giúp kiểm tra mã của bạn theo cách TDD dễ dàng hơn.

Các std::for_eachthuật toán có thể được đọc như làm một cái gì đó với tất cả các yếu tố trong phạm vi , mà có thể cải thiện khả năng đọc. Giả sử hành động mà bạn muốn thực hiện dài 20 dòng và chức năng thực hiện hành động cũng dài khoảng 20 dòng. Điều đó sẽ làm cho một hàm dài 40 dòng với một vòng lặp for thông thường và chỉ khoảng 20 với std::for_each, do đó có thể dễ hiểu hơn.

Functor cho std::for_eachnhiều khả năng là chung chung hơn, và do đó có thể tái sử dụng, ví dụ:

struct DeleteElement
{
    template <typename T>
    void operator()(const T *ptr)
    {
        delete ptr;
    }
};

Và trong mã, bạn chỉ có một lớp lót giống như std::for_each(v.begin(), v.end(), DeleteElement())IMO tốt hơn một chút so với vòng lặp rõ ràng.

Tất cả các functor thường dễ dàng có được các bài kiểm tra đơn vị hơn là một vòng lặp rõ ràng ở giữa một hàm dài, và điều đó một mình đã là một chiến thắng lớn đối với tôi.

std::for_each nhìn chung cũng đáng tin cậy hơn, vì bạn ít có khả năng phạm sai lầm với phạm vi.

Và cuối cùng, trình biên dịch có thể tạo ra mã tốt hơn một chút std::for_eachso với một số loại vòng lặp thủ công nhất định, vì nó (for_each) luôn trông giống nhau đối với trình biên dịch và các trình soạn thảo trình biên dịch có thể đưa tất cả kiến ​​thức của họ, để làm cho nó tốt như họ có thể.

Áp dụng tương tự cho các thuật toán tiêu chuẩn khác như find_if, transformv.v.


2

forlà vòng lặp có thể lặp lại từng phần tử hoặc mỗi phần ba, v.v ... for_eachchỉ để lặp lại từng phần tử. Rõ ràng từ tên của nó. Vì vậy, rõ ràng hơn những gì bạn đang có ý định làm trong mã của bạn.


4
không, nếu bạn vượt qua nó, một trình vòng lặp tiến lên 3 với mỗi lần lặp ++. Có thể không bình thường, nhưng một vòng lặp for cũng làm như vậy.
Potatoswatter

Trong trường hợp đó, có lẽ tốt hơn là sử dụng transformđể không gây nhầm lẫn cho ai đó.
Kirill V. Lyadvinsky

2

Nếu bạn thường xuyên sử dụng các thuật toán khác từ STL, có một số lợi thế để for_each:

  1. Nó thường sẽ đơn giản và ít xảy ra lỗi hơn một vòng lặp for, một phần vì bạn sẽ quen với các chức năng với giao diện này, và một phần vì nó thực sự ngắn gọn hơn trong nhiều trường hợp.
  2. Mặc dù một vòng lặp dựa trên phạm vi có thể thậm chí còn đơn giản hơn, nhưng nó kém linh hoạt hơn (như được lưu ý bởi Adrian McCarthy, nó lặp đi lặp lại trên toàn bộ một container).
  3. Không giống như một vòng lặp for truyền thống, for_eachbuộc bạn phải viết mã sẽ hoạt động cho bất kỳ trình lặp đầu vào nào. Bị hạn chế theo cách này thực sự có thể là một điều tốt bởi vì:

    1. Bạn thực sự có thể cần phải điều chỉnh mã để làm việc cho một container khác sau này.
    2. Lúc đầu, nó có thể dạy cho bạn một cái gì đó và / hoặc thay đổi thói quen của bạn để tốt hơn.
    3. Ngay cả khi bạn luôn viết cho các vòng lặp hoàn toàn tương đương, những người khác sửa đổi cùng một mã có thể không làm điều này mà không được nhắc sử dụng for_each.
  4. Việc sử dụng for_eachđôi khi làm cho rõ ràng hơn rằng bạn có thể sử dụng hàm STL cụ thể hơn để làm điều tương tự. (Như trong ví dụ của Jerry Coffin; không nhất thiết phải for_eachlà lựa chọn tốt nhất, nhưng vòng lặp for không phải là lựa chọn duy nhất.)


2

Với C ++ 11 và hai mẫu đơn giản, bạn có thể viết

        for ( auto x: range(v1+4,v1+6) ) {
                x*=2;
                cout<< x <<' ';
        }

như là một thay thế cho for_eachhoặc một vòng lặp. Tại sao chọn nó sôi sùng sục và an toàn, không có khả năng xảy ra lỗi trong một biểu thức không có ở đó.

Đối với tôi, for_eachluôn luôn tốt hơn trên cùng một lý do khi cơ thể vòng lặp đã là một functor và tôi sẽ tận dụng mọi lợi thế mà tôi có thể có được.

Bạn vẫn sử dụng biểu thức ba for, nhưng bây giờ khi bạn nhìn thấy một biểu thức bạn biết có gì đó để hiểu ở đó, nó không phải là bản tóm tắt. Tôi ghét nồi hơi. Tôi bực bội sự tồn tại của nó. Đó không phải là mã thực, không có gì để học bằng cách đọc nó, đó chỉ là một điều nữa cần kiểm tra. Nỗ lực tinh thần có thể được đo lường bằng cách dễ dàng bị rỉ sét khi kiểm tra nó.

Các mẫu là

template<typename iter>
struct range_ { 
                iter begin() {return __beg;}    iter end(){return __end;}
            range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
            iter __beg, __end;
};

template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
    { return range_<iter>(begin,end); }

1

Chủ yếu là bạn sẽ phải lặp đi lặp lại trên toàn bộ bộ sưu tập . Do đó, tôi khuyên bạn nên viết biến thể for_each () của riêng bạn, chỉ lấy 2 tham số. Điều này sẽ cho phép bạn viết lại ví dụ của Terry Mahaffey như:

for_each(container, [](int& i) {
    i += 10;
});

Tôi nghĩ rằng điều này thực sự dễ đọc hơn một vòng lặp for. Tuy nhiên, điều này đòi hỏi các phần mở rộng trình biên dịch C ++ 0x.


1

Tôi thấy for_each là xấu cho khả năng đọc. Khái niệm này là một khái niệm tốt nhưng c ++ làm cho nó rất khó viết, ít nhất là đối với tôi. c ++ 0x biểu thức lamda sẽ giúp. Tôi thực sự thích ý tưởng về lamdas. Tuy nhiên, thoạt nhìn tôi nghĩ cú pháp rất xấu và tôi không chắc chắn 100% mình sẽ quen với nó. Có lẽ trong 5 năm tôi sẽ quen với nó và không cho nó một ý nghĩ thứ hai, nhưng có lẽ là không. Thời gian sẽ trả lời :)

Tôi thích sử dụng

vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
  // Do stuff
}

Tôi tìm thấy một rõ ràng cho vòng lặp rõ ràng hơn để đọc và khám phá bằng cách sử dụng các biến được đặt tên cho các vòng lặp bắt đầu và kết thúc làm giảm sự lộn xộn trong vòng lặp for.

Tất nhiên các trường hợp khác nhau, đây chỉ là những gì tôi thường tìm thấy tốt nhất.


0

Bạn có thể có iterator là một cuộc gọi đến một chức năng được thực hiện trên mỗi lần lặp thông qua vòng lặp.

Xem tại đây: http://www.cplusplus.com/reference/alerskym/for_each/


2
Các bài viết chỉ liên kết không tạo ra câu trả lời hay, và dù sao, trong liên kết đó, nó hiển thị bất cứ thứ gì tương tự như một trình vòng lặp có thể gọi được? Tôi khá chắc chắn rằng khái niệm đó không có ý nghĩa. Có lẽ bạn chỉ tóm tắt những gì for_each, trong trường hợp đó, nó không trả lời câu hỏi về lợi thế của nó.
gạch dưới


0

for_eachcho phép chúng tôi thực hiện mô hình Fork-Join . Khác hơn là nó hỗ trợ giao diện lưu loát .

mô hình ngã ba

Chúng ta có thể thêm triển khai gpu::for_eachđể sử dụng cuda / gpu cho tính toán song song không đồng nhất bằng cách gọi tác vụ lambda trong nhiều công nhân.

gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.

gpu::for_eachcó thể đợi các công nhân làm việc trên tất cả các nhiệm vụ lambda để hoàn thành trước khi thực hiện các báo cáo tiếp theo.

giao diện lưu loát

Nó cho phép chúng ta viết mã có thể đọc được theo cách ngắn gọn.

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
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.