web-dev-qa-db-ja.com

スマートポインターを使用するように大規模なC ++コードベースを移行する手順

私は大きなC++コード(〜2300ファイル、〜600K行、主に古いC/C++ 98スタイルのコード)をリファクタリングしている最中で、C++スマートポインターを使用して補強できるメモリリークが確実にあります。スマートポインターへの移行に向けた段階的な経路はありますか、それとも「オールオアナッシング」の提案ですか?

たとえば、すべての「ファクトリクラス」は_std::unique_ptr_を返す必要がありますが、これにより、(適切に)すべての呼び出し元が結果を_std::unique_ptr_として保存するよう強制されます。しかし、ローカルコードは、それを処理するために生のptr(ローカルの弱いptrとして扱われる)を取得するだけです。 (複数の)データ構造内にポインタを格納する場合、_std::shared_ptr_を使用する必要がある同様のパスをたどることもできるようです-たとえば、_std::shared_ptr_(および_std::weak_ptr_を使用して後方参照)。ローカルウィークポインターには生のポインターを使用します。

5
wcochran

徐々に移行することはできますが、後で境界をクリーンアップするのは難しい場合があります。

古いコードを書き直す前に、C++コアガイドラインを参照してください。非常に賢明なガイダンスが数多く提供されているためです。例えば:

多くの場合、参照を使用する関数の引数を除いて、生のポインタを使用することは、特に借用/一時的な所有権を示すために依然として適切です。

したがって、一度に1つの関数とファイルを移行し、内部とインターフェースを最新化することをお勧めします。スマートポインターとの間で変換を行うときに、呼び出しサイトをこれらの関数に修正します。たとえば、関数T* create()unique_ptr<T> create()に更新すると、呼び出しサイトはcreate()からcreate().release()に更新されます。その部分も再近代化します。

残念ながら、_shared_ptr_が削除の原因となるため、共有所有権ではこれはあまりうまく機能しませんが、理想的には、このような複雑な所有権が発生することはほとんどありません。すべての共有者を一度に更新する必要があります。疑わしい場合は、これらの複雑な部分を後で使用するために残し、最初に、スマートポインターが大きなリスクなしに貴重な明快さをもたらす機能を攻撃します。

もちろん、一般的なリファクタリングのベストプラクティスが適用されます。例:

  • コードを変更する前にいくつかのテストを行う
    • 非常に表面的なテストでさえ、多くの助けとなり、詳細なアサーションを作成することさえせず、80%を超えるラインカバレッジを目指してください
  • 小さな変更を行い、早い段階で頻繁にコミットし、プロジェクト全体を稼働状態に保ちます
    • このルールを尊重しないことで、リファクタリングの取り組みがあまりにも多く失敗しました。リファクタリングの作業を2倍にすることは、他の重要な作業を停止させないことを意味する場合には安上がりです。
  • 一時的な互換性シムを使用して、コードを実行可能に保ちます。
    • 例えば。 T* create()unique_ptr<T> create()に直接変更する代わりに:
      1. unique_ptr<T> create2(); T* create() { return create2().release(); }のような互換性レイヤーを作成することから始めます。
      2. 次に、すべての呼び出しサイトをcreate2()に徐々に移動します。
      3. 次に、_create2_→createの名前を一括変更します。

この取り組みにおける大きな「リスク」は、メモリの安全性の問題に遭遇することです。解放後使用。その場合は、すぐに修正するのが最も簡単です。これは、インクリメンタルリファクタリングアプローチと連携します。これにより、すべての2kファイルの処理が完了する前に、メンテナンスリリースを作成できるようになるからです。

リファクタリングの範囲が広い場合、スマートポインタに完全に移動するのは賢明ではないかもしれません。これは、実質的な価値をもたらす場合にのみ行ってください。スマートポインタの価値は、正しいコードを簡単に記述できることです。したがって、将来変更される可能性のあるコンポーネントに集中して取り組みます。対照的に、10年間ほとんど変更されていない場合は、コードを変更しないでください(現時点では)。

9
amon