web-dev-qa-db-ja.com

C ++プロジェクトでCスタイルのキャストを使用する場合、C ++キャストにリファクタリングする価値はありますか?

私は15KLOC C++プロジェクトでCスタイルのキャストを使用しています。これは、子クラスと基本クラスの間のキャストに90%の確率で使用されます。

それらを使用するのは悪いことであり、C++のキャストのようにタイプセーフではないため、重大なエラーが発生する可能性があることを読んだ場合でも、私はまだfeel完全に問題なく使用できます。

これまでのところ、プロジェクトでバグを1つも経験していません。これは、たとえば、誤ってタイプミスしたCスタイルのキャストが原因でした。

私がそれらを使用していない主な理由は2つあります。

  • 私はまだそれらについて十分に知りませんでした
  • 私はそれらの構文が好きではありませんでした、それらはより冗長で私にとって読みにくいです

私の質問:

  • (なぜ)C++スタイルのキャストを使用するようにプロジェクトをリファクタリングする必要がありますか?
  • 将来のプロジェクトでC++スタイルのキャストを使用する必要があるのはなぜですか?

新しい型キャスト構文だけでなく、仮想および抽象基本クラス、名前空間、STLなどを含むOOPから、C++が提供する他のすべての利点と同じくらいうまく使用しています。引数 " Cを使用しないのはなぜですか? "は私には機能しません。

65
Ymir

C++スタイルのキャストの主な利点は、前述のように、型の安全性です。 C++の各キャストは、特定の種類の変換(または関連する変換のファミリー)を処理するため、コンパイラーは、意図した変換を誤って行っていないか、または基本的に安全ではない一連の変換を行っていないかを確認できます。 。

考えておくべきことの1つは、Cスタイルのキャストを快適に使用できることは素晴らしいことですが、間違いを犯していないことは素晴らしいことですが、コードベースで作業している他の人はこれらのキャストにそれほど慣れていない可能性があります。あなたがそうであるように。キャスト演算子を使用すると、コードがより自己文書化されます。どこかにCスタイルのキャストがある場合、コードを読んでいる他の誰かが、あなたが何をしているのかをすぐに推測できない可能性があります。彼らが次のようなものを見たら

T* ptr = (T*) var;

彼らはこれがそうであるかどうかすぐに見分けることができないかもしれません

  1. 基本クラスから派生クラスへのキャスト、またはその逆。
  2. constからvarnessを取り除くキャスト
  3. 整数型からポインタへのキャスト。

彼らはおそらくこれを文脈から収集することができますが、次のようなキャストを使用すると何が起こっているのかがはるかに明白になります

T* ptr = static_cast<T*>(var);

または

T* ptr = const_cast<T*>(var);

C++スタイルのキャスト演算子を好むもう1つの理由は、コードの変更に対する耐性が向上することです。たとえば、次の関数があるとします。

void DoSomething(Base* ptr) {
    Derived* derived = (Derived *) ptr;
    DoSomethingElse(derived);
}

ここで、この関数が引数に変更を加えることは想定されていないことに気付いたとしたら、constとマークすることにしました。例えば:

void DoSomething(const Base* ptr) {
    Derived* derived = (Derived *) ptr;
    DoSomethingElse(derived);
}

しかし、今は問題があります。以前はダウンキャストを行っていたCスタイルのキャストでも、constnessが削除されます。これは、DoSomethingElseメソッド自体がこれを行わないことを約束している場合でも、DoSomethingが渡したポインターを変更するという簡単なバグにつながる可能性があります。代わりに私がこのコードを次のように書いた場合

void DoSomething(Base* ptr) {
    Derived* derived = static_cast<Derived *>(ptr);
    DoSomethingElse(derived);
}

次に、constを追加してコードを変更します。

void DoSomething(const Base* ptr) {
    Derived* derived = static_cast<Derived *>(ptr);
    DoSomethingElse(derived);
}

これで、古いキャストが壊れていることを通知するコンパイラエラーが発生します。これにより、コードに論理エラーがあることがわかります(つまり、DoSomethingElseが引数を変更するため、次のことができます。」素朴にptrを-constへのポインタにします。

つまり、C++キャスト演算子を使用すると、コードが読みやすく、保守しやすくなります。これにより、コードの背後にあるロジックがより明確になります。また、エラーを作成しているとき、または後で戻って古いコードを変更するときにコンパイラーにエラーをキャッチさせることで、コードのバグが発生しにくくなります。これらの主な理由から、将来的にはC++スタイルのキャスト演算子を使用することをお勧めします。

戻って現在のCスタイルのキャストをC++スタイルのキャストに置き換えようとするかどうかについては、それは本当にあなた次第です。演習として、使用しているキャストの種類を学習する練習をすることをお勧めします。さらに、そこに論理エラーが見つかる可能性があります。これにより、検索する価値があります。

89
templatetypedef

私が以前働いていた会社では、かつて数百万行のコードアプリを新しいプラットフォームに移植する必要がありました。 「別のUnixプラットフォームへの別のポート」(コードはすでにWindows、OSX、および5ダースのUnixライクなプラットフォームで実行されています)として始まったものは、大きな問題になりました。 IIRC、その理由は、このプラットフォームにはかなり厳密な配置要件があり、キャストの一部がトリップしていたためですハードウェアの例外ミスアライメントによる。

これらのキャストの特定の割合がCスタイルのキャストであるという事実がなければ、これは修正するのに十分簡単だったでしょう。 )。これらに対して効果的にgrepすることは不可能でした。検索は文字通り数万行のコードであり、すべて人間が手動で検査、その99.99%はまったく無関係でした。
私たちが人間が見逃しがちだったこれらの見つけにくいキャストのいくつかは助けにはなりませんでした、別のレビューラウンド数万行のコード、そのうちの99.99%で見つける必要があります最初のラウンドとまったく同じです。

その仕事は与えられた時間内に完了することはほぼ不可能でした、そして彼らは厳しい状況に直面していたので会社の背中をほとんど壊しました期限を過ぎた場合の契約ペナルティ。

言うまでもなく、その後、すべてのCスタイルのキャストを対応するC++に置き換えることが決定されました。ただし、そのポートを作成する前に、このポリシーが存在していました...

33
sbi

(なぜ)C++スタイルのキャストを使用するようにプロジェクトをリファクタリングする必要がありますか?

はい。同じ理由で、将来それらを使用する必要があります。

将来のプロジェクトでC++スタイルのキャストを使用する必要があるのはなぜですか?

リストは実際にはかなり長いですが、最も重要なものについて説明します。

まず、彼らはキャストの意図を明確に述べています。 「static_cast」を読むと、作成者が再解釈やconstではなく、静的キャストを実行することを意図していることがわかります。

第二に、彼らはたった一つのことをします。たとえば、誤って恒常性を捨てることはできません。

それらは簡単に検索できます。 「const_cast」を検索して、constとの間のすべてのキャストを検索します。 Cスタイルでこれをどのように行いますか?あなたはできませんでした。

ローカルコードのセマンティクスが変更されても、キャストの種類は変更されません。例えば、 (Widget*)(blah)ウィジェットがポインタであったタイプからの継承を停止すると、サイレントに再解釈キャストに変更されます。また、ウィジェット定義がローカルで利用できない場合は、再解釈キャストに変更されます。代わりに新しいスタイルのキャストを使用すると、ステートメントをサイレントに再解釈するのではなく、コンパイラーの嘔吐が発生します。

ダイナミックキャストができます。 Cスタイルのキャストはこれを行うことができません。

10
Edward Strange

よく2つの部分:

(なぜ)C++スタイルのキャストを使用するようにプロジェクトをリファクタリングする必要がありますか?

コードが機能する場合。その後、それを修正しないでください。そのままにしておきます。とにかく、Cキャストを見つけるのはかなり難しいかもしれません。コードで作業していてCキャストに出くわした場合は、必要に応じて変更してください。

将来のプロジェクトでC++スタイルのキャストを使用する必要があるのはなぜですか?

Cキャストは間違えやすいからです。

9
Martin York

キャストの90%がサブクラスからベースクラスである場合、それらを削除することから始めます。これらは必要ありません。ベースからサブへのキャストがたくさんある場合は、何か別のことをしていることになります。

残りは、何か奇妙なことが起こっていることをコンパイラに伝えており、ルールを破っています-reinterpret_castまたはconst_castを使用した型システムのルールでは、static_castを使用してより小さな整数型に表現するリスクがあります、またはLSPを破っていて、基本クラスが与えられたときにサブクラスの特定のタイプについて知る必要があります。

これらのケースを明示的にすることは良いことです。

4
Pete Kirkham
(Why) Should I refactor my project to use C++-style casts?
Why should I use C++-style casts for my future projects?

この質問 の2番目の質問に対するいくつかの良い答え(私自身のIMOを含む)があります。

最初の質問では、私があなたの立場にある場合、すでにファイルを編集しているときはいつでも、CスタイルのキャストをCスタイルのキャストに置き換えます。すべてのコードを編集して再コンパイルするのに1日ほど取っておくことはおそらく価値がありませんが、すでに作業しているときにコードベースを段階的に改善することはおそらく価値があります。

リファクタリングアクティビティのために時間を取っておけば、これはそのための良い候補になります。

3
JohnMcG

私があなただったら、Cスタイルのキャストを使い続けます。あなたには正当な理由があります:あなたはCスタイルのキャストの疑惑の欠陥を経験していない、C++キャストはより冗長であり、C++キャストは読みにくく、あなたにはあまり馴染みがありません。

より一般的には、C++プログラマーとして、物事を行うための何らかの方法は古い、間違った、Cの方法であり、新しい、正しい、C++の方法を使用する必要があることをよく読んだり言われたりします。いくつかの例:

  • cスタイルのキャストの代わりにC++スタイルのキャストを使用する必要があります
  • ポインタの代わりに参照を使用する必要があります
  • 「const」キーワードを多用する必要があります
  • voidポインタやタイプキャストの代わりにジェネリックを使用する必要があります
  • コンストラクターの本体でメンバー変数を初期化する代わりに、初期化子リストを使用する必要があります
  • 構築後の初期化関数の代わりにコンストラクターを使用する必要があります
  • マクロは絶対に使用しないでください

通常、新しい方法の方が安全であり、コンパイラがより多くのエラーをキャッチできるようになり、コードがより明確になると言われます。

私はあなたがそのようなアドバイスを疑いを持って見ることを提案します。代わりに、次のことを検討してください。新しいC++機能を使用すると、実際に発生している問題が解決されますか?それは新しい問題を引き起こしますか(ちなみに、コードの冗長性の増加と読みやすさの低下は問題です)?最初の質問への答えが「いいえ」である場合、または最初の質問への答えが「はい」である場合は、古い、間違った、Cの方法に固執することを検討する必要があります。

コードの明快さに関する補足コメント:追加されたテキストが読者がプログラムを理解するのに役立たない場合、冗長性は明快さを低下させます。私があなたなら、C++スタイルのキャストはコードの明快さを懐疑的に改善するという主張を見るでしょう。なぜなら、あなたのプログラムの読者であるあなたは利益を認識していないからです。

私はかつて、常に新しい、正しい、C++の方法を使おうとするような人でした。私はもはやそれほど独断的ではありません。以下のリンクのエッセイは私の心を変える大きな要因でした。

http://yosefk.com/c++fqa/

2
Russell Silva