生のループと STL アルゴリズム

先週から、静的 HTML ページの CMS に再び取り組んでいるので、Qt とブーストを使用したアプリケーションの構築に関するシリーズは続きます。今日は、STL アルゴリズムの使用について、または Sean Parent がかつて「生のループはありません!」と言った方法についてです。さて、私は Sean Parent ではありませんし、STL の実装者でさえ完璧ではありません。私が書くほとんどのコードは、Meeting C++ を強化するアプリケーション コードです。また、私はすべての STL アルゴリズムを知っているわけではなく、STL で特定のアルゴリズムを検索する代わりに、ちょっとしたループを書きたくなることもあります。昨日、そのようなケースがありました。

メニューの生成に取り組んでいます。これは、boostache を使用して実際の HTML を生成するための重要な部分です。したがって、これに関連して、2 つのページ間の相対パスを見つける問題があります。ディレクトリはツリーを形成し、各ディレクトリには少なくとも 1 つの HTML ページがあります。 2 つのページがある場合、ページ A と現在のページの間の相対パスは?

生のループ ソリューション

これを一度に解決する STL アルゴリズムはありません。相対パス機能があるので、boost::filesystem を使用することを考えましたが、私のデータ モデルは実際にはディレクトリとページのツリーであるため、boost:::filesystem は、存在しないパスでも機能します。結局、単一のファイルが書き込まれる前に、このコードを実行する必要があります。

raw ループ ソリューションのコアは、3 つの raw ループです。

auto& vec_cp = dircache[current_page->id()];//menu page dir1/dir1_1/p
Page* p = node->get< Page >();
auto& vec_tp = dircache[p->getId()];// this page  dir1/dir1_2/p
size_t same =0;
while(vec_cp.size() > same && vec_tp.size() > same && vec_cp[same] == vec_tp[same])
    ++same;
std::string path;
for(size_t diff_cp = vec_cp.size() - same;diff_cp > 0;--diff_cp)
    path += "../";
for(size_t diff_tp = vec_tp.size() - same; diff_tp + same < vec_tp.size(); ++diff_tp)
    path += vec_tp[same + diff_tp] + "/";
path+= p->getName() + ".html";

ループと変数宣言を数えると、生のループは 7 loc であり、最初の for ループを置き換える巧妙なトリックがあるのか​​もしれません。どちらの for ループも for_each として簡単に記述できますが、while ループとは何ですか?これを処理できる STL アルゴリズムはどれですか?同時に 2 つの範囲で実行し、どちらの範囲が最初に終了するかも確認しますか?

STL アルゴリズム ソリューション

前述したように、for ループを std::for_each に変換し、本体にラムダを使用するのは簡単です。 while ループは std::mismatch に置き換えられ、値が一致しない最初のイテレータのペアが与えられます:

auto& vec_cp = dircache[current_page->id()];//menu page dir1/dir1_1/p
Page* p = node->get< Page >();
auto& vec_tp = dircache[p->getId()];// this page  dir1/dir1_2/p
auto it_pair = std::mismatch(vec_cp.begin(),vec_cp.end(),vec_tp.begin(),vec_tp.end());
std::string path;
std::for_each(vec_cp.begin(), it_pair.first,[&path](const std::string&){path += "../";});
std::for_each(it_pair.second, vec_tp.end(),[&path](const std::string& s){path += s +"/";});
path += p->getName() + ".html";

このコードは C++14 バージョンの不一致を使用します。以前の標準にはバージョンがありません。2 つの範囲のどちらが短いかは関係なく、最初の範囲は短い方でなければなりませんでした。 STL ソリューションはわずか 3 行のコードであり、インデックスの代わりに反復子を使用します。変数は同じで、カウンターはもう必要ありません。

Ranged for ループ

ループ用の新しい派手な C++ 11 の範囲は生のループでもありますか?範囲指定された for ループは、std::transform または std::for_each に置き換えることができます。これらのアルゴリズムとは異なり、for ループは通常、範囲全体でのみ実行されます。 STL アルゴリズムは、開始と終了に関してより柔軟です。 Sean Parent は C++ Seasoning の講演で、transform と for_each の役割で範囲ベースの for ループを使用しても問題ないと述べています。

  • for_each -> for(const auto&&item:items)
  • transform -> for(auto&&item :items)

コンテナ全体を反復処理する必要がある場合は、範囲ベースの for ループの方が読みやすく、次に変換または for_each の方が読みやすいと思います。だから私はそれを好みますが、常に 2 つのうちの 1 つである必要があります。

重要な違いとして、生のループ内では、特定のキーワードがループの動作を変更できるということがあります:break、continue、return です。通常、これらには if/else が伴い、多くの場合 *_if アルゴリズムに置き換えられます:

  • remove_if
  • remove_copy_if
  • copy_if
  • find_if
  • count_if
  • replace_if
  • replace_copy_if

または、単純に、rotate、partition、および any_of、all_of、none_of などのアルゴリズムの述語内に適用できますか?

では生のループはありませんか?

範囲ベースの for ループは、C++ で C から継承されない唯一のループです。そのため、生のループを作成するときはいつでも、コードは (クラスを使用して) C に近づき、現在理解されているように C++ に近づきます。 STL は、ループのさまざまなユース ケースを名前付きアルゴリズムでラップするため、コードが読みやすくなります。使用されているアルゴリズムは、コードが何をしているかを示しています。アルゴリズムを使用し始めると、生のループは定型コードのように見えます。

ただし、STL アルゴリズムを適用できるのは、そのためのインターフェイスがある場合のみです。これは開始イテレータと終了イテレータを意味します。 Qt を使用していると、QListWidget のように、アイテム n にアクセスするためのカウントとメソッドのみを取得する場合があります。

for(int i = 0,s=ui->lst_feeds->count();i < s; ++i)
{
    auto* item = ui->lst_feeds->item(i);
    auto si = item->data(Qt::UserRole).value< FeedItem::SharedItem >();
    if(si && si->contains(list))
        item->setCheckState(Qt::Checked);
}

このコードは、選択された現在のデータセットにリスト項目があるかどうかをチェックします。 QListWidget は、リストのパネル内に利用可能なフィードを表示するため、リストを選択すると、どのフィードが表示されるかを確認できます。

しかし、もちろん、これに対する解決策もあります。STL に対する反復子ファサードを持ち、QListWidget のアクセス パターンをラップするプロキシを作成できます。

したがって、ほとんどの人にとって、変更できないレガシー コードを除いて、生のループではない可能性があります。