私の最初の仕事経験


私は最近新しいチームに加わった.我々は、ライブラリと同様に我々自身の内部のマイクロサービスを持っています.MicroServicesでは、1つのメインブランチをサポートしています.ライブラリの場合は、少なくとも3つのバージョンをサポートしなければなりません.
別のリリースでは、異なるバージョンのC++をサポートする異なるツールチェーンを使用しています.C++ 11から、C++ 20までのすべてのバージョンがあります.私は既にC + 20を自分で勉強していましたが、現実世界の企業の文脈でそれを使う機会はありませんでした.実際、C++ 17でさえありません-それはそれほど多くのNoveltiesを提供しません.
この小さなポストでは、私たちの革新的な週に私はいくつかのcodebase近代化に費やすことができる反映したいと思います.

C + + 11でさえない


新しいバージョンを使用するだけでなく、L 'アートはL 'アートを注ぐ.新しい標準を使用すると、かなりのコードを簡素化する必要があります、それはメンテナの生活を容易にする必要があります.私たちのcodebaseにC++ 11を導入した長い年、私はほとんどループのためにベースの範囲の使用を見つけました.さて、ループのための範囲ベースは重要なバグを持っています、しかし、私は明らかに、それがこれらの読み込み可能なループを持っていないのが理由であることを疑います.
代わりに、反復子の多くの長い構文を見つけました.あるいは、古いものをループに対しても、添字演算子と一緒にインクリメントされたインデックスを使用していました.[] ).
そして、私はスマートポインタを使用することの不足さえ言及しませんでした.default member initalization , など

マップとセットが含まれて


あなたが働かなければならないならばstd::map or std::set またはそれらの順序なしのバージョンでは、おそらくどのように面倒な知っているかどうかは、特定の項目を持っているかどうかを見つけることです.を使うfind() そして、結果をend() イテレータはverboseです.
C + 20を使用すると、それらをすべて置き換えることができます contains !
std::map<std::string, int> myMap;
// ...

//before C++20
if (myMap.find(aKey) != myMap.end()) {
    // the map contains a key
    // ...
}

// with C++20
if (myMap.contains(aKey)) {
    // ...
}
もちろん、その項目にイテレータを必要とする場合は、まだ使用する必要がありますfind , でもcontains 多くの場合、コードを簡素化します.

構造化バインディングによる反復マップの反復


タイプが非常に長いので、ループの外側でイテレータを作成したことがよくわかりました.次に、ループ本体の最初の行で、指定されたキーと値を参照しましたmap 要素.
std::map<std::string, SomeLengthClassName>::const_iterator aIt;

for (aIt = myMap.begin(); aIt != myMap.end(); ++aIt)
{
    const std::string& aKey = aIt->first;
    const SomeLengthClassName& aValue = aIt->second;
    // ...
}
C + 17では、私たちは使用できますstructured bindings そして、これらの複雑なループを取り除くことができます.
for (const auto& [aPhase, aValue]: myMap)
{
    // ...
}
それは短くて、より読みやすいです.
しかし、あなただけのキーまたは値を必要とするときに行う必要がありますか?

範囲と行方不明


しかし、我々が我々がキーまたは値を使わないとき、我々がC++ 20ですることができるより多くがあります!
構造化されたバインディングの概念を続けて、キー値ペアのいずれかを必要としない場合、C + 17を使用すると、単純な名前には_ . C++ 20の範囲ではなく、これらの可能性があります!
std::map<std::string, int> myMap {{"one", 1}, {"two", 2}, {"three", 3}};
for (auto aIt = myMap.begin(); aIt != myMap.end(); ++aIt)
{
    std::cout << aIt->second << '\n';
}


for (auto const& aValue: std::views::values(myMap))    
// or...
for (auto const& aKey: std::views::keys(myMap))

これはすでに読みやすくなっており、Linux上で動作するプログラマにとって満足のいくものでなければならない「パイプ構文」を使用しようとしていない.
for (auto const& aValue: myMap | std::views::keys) {
       std::cout << aValue << '\n';
}
このパイプの構文は、我々がチェーンの複数のアルゴリズム、ビューなどを一緒にして、最初の範囲の周りの層を構築するのではなく、我々は単に左から右に読むことができるとすぐに何が起こって理解する可能性が最善の可能性を示しています.この関数は、ranges and views 名前空間は、イテレータのペアを直接に取りません.もう一つの記事のそれの上でより多く.
良い古い方法、構造化されたバインディングを持つループと範囲/ビューのパフォーマンスに違いがありますか?
私はクイックベンチで若干の分析をしました、そして、私はキーまたは値の上で繰り返しているC++ 17とC++ 20の方法の違いを見つけませんでした、しかし、彼らはイテレータで手動で扱うより少しも速いです.
驚いたことに、私は多くの用法を見つけなかったstandard algorithms . しかし、私がしたとき、私はほとんど常に範囲版とそれらを交換することができました.
私はすでに範囲がどのように私はマップのキーを繰り返したり、標準的な範囲ベースのアルゴリズムと簡単な標準的なアルゴリズムを置き換えることができますループを簡素化するのに役立つことを示した.
std::copy(myVec.begin(), myVec.end(), std::back_inserter(results));

// The above example would become
std::copy(myVec, std::back_inserter(results));
一目では、範囲のバージョンには、小さなパフォーマンスのペナルティがあるようです.さらに分析しなければならないこと.ほとんどの時間はデータベースやネットワークのクラスで失われていたが、アプリケーションでは重要ではない.
いずれにせよ、読みやすさの増加は、CPU時間の少しの損失を正当化するかもしれません.それはあなたの状況次第です.
私は、ループのために完全に取り替えたいとき、範囲が最高であるとわかりました.例をあなたに分かろう.
bool Configuration::warnOnMissingData(const Date& iCheckDate)
{
    bool aWasAWarningIssued(false);

    Date aLastValidDate;
    std::vector<ImportantData>::const_iterator aDataIterator;
    for (aDataIterator = _data.begin(); aDataIterator != _data.end(); ++aDataIterator)
    {
        aLastValidDate = aDataIterator->second->getLastDate();
        if (aLastValidDate < iCheckDate)
        {
            LOG_ERROR(aDataIterator->second);
            aWasAWarningIssued = true;
        }
    }

    return aWasAWarningIssued;
}
そのループは決して偉大ではなかった.なぜ我々は最初のマッチング条件の後ループを保つのですか?多分ログのため?それはすばらしい説明ではない.C++ 11でさえ、上記のループを単純化するための大きなオプションがありました.しかし、働くコードを変える時間を見つけるのは難しいです.しかし、あなたがするとき、恥ずかしがらないでください.コードがテストされていることを確認し、あなたの最高の知識に応じてリファクタリング.
bool Configuration::warnOnMissingDataKeys(const Date& iCheckDate)
{
    auto isDataLastDateOlderThan = [&iCheckDate](const auto& aData) {
            if (aData == nullptr) {
                    return false;
            }
            return aData->getLastDate() < iCheckDate;
        };
    const auto& aPotentialMatch = std::ranges::find_if(
            _data,
            isDataLastDateOlderThan,
            &std::vector<ImportantData>::value_type::second
    );
    if (aPotentialMatch == _data.end()) { return false; }
    LOG_ERROR(aPotentialMatch->first);
    return true;
}
このリファクタリングを用いて原ループの代わりにアルゴリズムを導入し,条件にも名前を与えることができた.我々は、おそらく意味さえされなかったいくつかのログを失った.

テンプレートの概念


最後に、少なくとも、私はT.10 core guideline 'テンプレートのパラメータがないことを推奨します.それらの各々は、現在いくつかの概念によって制約されます.時々私は標準的な概念を使用したが、しばしば私は最初に自分の概念を作成する必要がありました.
どのように、私はこれらの新しい概念について来ましたか?
私はテンプレートにテンプレートパラメータをどのように使用するかを見るために深く調べました.それによって、どんなタイプから必要なAPIを理解しました.それから、私はパターンを見つけることができるかどうかを確認するために、各インスタンス化を見ました.私が必要とするAPIは、それぞれのテンプレート引数型がベースとして使われている抽象基底クラスで定義されたAPIであることがよくわかりました.
この事実を知っているならば、私はもう一度インターフェースを記述したかったか、ちょうど入って来るパラメタがそのベースクラス、そのインタフェースを実装しているのをただ必要とするかどうか決めさせました.最終的に、私は、それがちょうどインターフェースのためにあるならば、基本クラスを取り外すことについて考えるかもしれなくて、概念にそれを変えて、使用されている子供クラスがそのベース制約を満たすことを確認します.私は基本的にアヒルのタイピングを導入しますが、私はいくつかの仮想テーブルとポインタとランタイムインターフェイスを一般的に削除します.
しかし、概念の創造に戻ってみましょう.私がこの調査の2、3回を持っていたときだけ、私は概念のために良い名前で来ることに集中することができました.私は、この部分が最も難しいものを見つけました.名詞または形容詞を使うべきですか?私はその質問に全く賛成ではない.これまでのところ、少しだけ読むようになった名詞を使いました.あなたはどう思いますか.

結論


この記事では、C + 20と生産コードの最初の経験を共有しました.私はC++機能を紹介していない.
いくつかのケースでは、C++ 17が十分である.C++ 20のようないくつかの大きなライブラリ機能を導入contains マップとセットのためにranges 図書館と概念.すべてのこれはいくつかの学習が必要ですが、大幅にコードを簡素化することができます.

接続深い


この記事が好きなら
  • ボタンのようにヒット

  • subscribe to my newsletter
  • そして、接続しましょう!