web-dev-qa-db-ja.com

C ++でconstをもっと使うべきですか?

私にはかなり厳格な点がありますが、constはその1つではありません。

たとえば、関数で3回使用するローカル変数を作成する場合がありますが、変更されませんが、constにする必要はありません。

これは私がもっと厳しくすべきものですか?知っておくべき具体的なメリットはありますか?これは、私のコードを確認している場合にフラグを立てるものですか?

7
Akiva

これは私がもっと厳しくすべきものですか?知っておくべき具体的なメリットはありますか?

@JerryHeremiahが指摘するように、しばらくするとコンパイラの最適化のための追加は行われませんが、読みやすさが向上します。

私はこれを行う傾向があります(可能な場合はconstを使用して読みやすくします)。これはデメテルの法則に適合しているため(「この値を変更する必要がない場合は、この時点以降は許可しないでください」)。 (コンパイラーがチェックする方法で)著者の意図をよりよく表します。

これは、私のコードを確認している場合にフラグを立てるものですか?

プロジェクトの合意されたレビューコード基準に依存します(はい、これは私がフラグを立てるものですこれがプロジェクトのコーディング標準に対してフラグを立てる条件であった場合、そして私も主張しますこれはあるはずですプロジェクトにフラグを立てる条件です)。

5
utnapistim

インターフェースを正しく使いやすく、誤って使いにくいようにする

constを使用すると、それだけを実行できます。関数が、関数の呼び出し元が変更を許可されていないアイテムのセットを返す場合、ドキュメントを読む呼び出し元に依存せず、const items、resp const_iterator。発信者が間違いを犯す可能性が低くなるだけでなく、constを使用すると、発信者がアイテムを変更してはならないというコメントを書くよりも入力がはるかに少なくなります。

constの「問題」は、伝播することです。 コードがconstで正しくない場合、コードを使用しているコードがconstであることがはるかに難しくなります。したがって、constの正しいパブリックインターフェイスを記述しない場合は、脆弱なハックを使用するか、constの正しいコードを記述しないように参加することで、私の回答の上部にある引用の多くの違反が発生します。

公的にアクセス可能なインターフェースの外では、constの伝播についてそれほど心配する必要がないため、実際に収益が減少しています。ただし、これらのケースでは、constをドキュメントの形式と見なします-読み書きが簡単なドキュメントです。コードが十分に取るに足らないものであれば、そのドキュメントは必要ありませんが、すべてではないにしてもほとんどの場合constを書く方が、コードが省略できるほど簡単であるかどうかを判断するよりもはるかに簡単です。それ。

4
Peter

変更されないローカル変数は、可能であればconstexprであり、そうでなければconst参照である必要があります。変数を変更する場合は、それが参照かローカルコピーかを本当に知る必要があります。あなたがそれを間違っていると、あなたのプログラムはあなたが期待することをしません。ただし、変数がconstである場合、それが参照であるかコピーであるかは問題ではありません。言語は、const refを一時変数にバインドできるようにすることでこれをサポートします。

// some functions
int& by_ref();
int by_val();

int main(){
  {
    auto a = by_ref();  // takes a copy
    const auto b = by_ref();  // also takes a copy
    auto& c = by_ref();  // no copy
    const auto& d = by_ref();  // no copy 
  }
  {
    auto a = by_val();  // might copy
    const auto b = by_val();  // might copy
    auto& c = by_val();  // error reference to temporary
    const auto& d = by_val();  // allowed! no copy.
  }
}

割り当てが参照によるものか値によるものかによって、a、b、cの動作がすべて異なることに注意してください。しかし、dはどちらの場合も同じです。
関数が参照を返す場合、dはその値を参照します。値を返す場合、dは一時オブジェクトを参照し、dがスコープ外になるまでそのオブジェクトの存続期間が延長されます。

したがって、可能な限りconst auto&を使用することにより、コードは残りのコードの変更に対してより堅牢になります。コードを壊さずに戻り値を参照から値に変更したり、余分なコピーを発生させずに値から参照に変更したりできます。

関数内でクラスのインスタンスを作成する場合、const参照の使用は少し奇妙であり、誰かがそれを読むのを混乱させる可能性があります。組み込み型の場合、constをマークしてもパフォーマンス上のメリットはおそらくないことはすでに指摘されていますが、より複雑な型の場合はそうする理由があります。以下を検討してください。

class my_class{

protected:
    struct my_data {
      int x, y, z;
    };
    ~my_class() = default;

private:
    virtual my_data& data() = 0;
    virtual const my_data& data() const = 0;

public:
    auto& x() {
      return data().x;
    }
    auto& y() {
      return data().y;
    }
    auto& z() {
      return data().z;
    }

    auto& x() const{
      return data().x;
    }
    auto& y() const{
      return data().y;
    }
    auto& z() const{
      return data().z;
    }
};

My_classですべてを2回定義したことがわかります。私は怠け者なので、正当な理由なしにそれをするつもりはありません。その理由は、my_classのconstインスタンスは、constというラベルの付いたメソッドにしかアクセスできないためです。したがって、いくつかの関数のconstバージョンとnon constバージョンの両方を提供する必要があり、私の自然な本能は、本当にすべての型付けを行う必要があるかどうか疑問に思うことです。

このバージョンでは、すべての関数のconstバージョンが必要であることは明らかです。それは明らかに抽象概念であり、どのように実装または使用されるかわかりません。誰かがどこかでconst my_classを望んでいても、すべてのメソッドを使用できるようにする必要があると想定するのは妥当です。

しかし、私は通常、テンプレートを介して抽象化することを好むので、ローカルでのみ使用する、または構文の奇妙なビットの回避策として使用する小さなクラスを時々持っているので、非constバージョンののみを提供するのは本当に魅力的です関数であり、非constインスタンスのみを使用します。

それが機能しなくなるまで、それはうまくいきます。ある時点で、何かのconstバージョンが私のクラスのconstバージョンをインスタンス化すると、テンプレートメタプログラマーが非常に愛する戦争と平和のスタイルのエラーメッセージの1つが表示されます。

したがって、道徳は常にメンバー関数のconstバージョンとnon constバージョンを定義します。理由はmy_classの例とまったく同じですが、すべてのコンテキストで常に完全に明らかであるとは限りません。残念ながら、なぜ何かをすべきかが明らかでない場合は、通常、それを行わないことがエラーやバグの原因であるかどうかは明らかではありません。

したがって、変数を変更する必要がない限り、常に変数をconstとして宣言すると、その関数のconstバージョンを持たないクラスをキャッチすることになります。これは、エラーを自分で修正する必要があるほど早くエラーをキャッチすることがわかっているため、最初にそれらを配置することに消極的ではなくなることを意味します。

自分でクラスを定義しなかったとしても、constとnon constのいくつかのメソッドのバージョンが微妙に異なる場合があります。

通常、クラスのconstメンバー関数とnon constメンバー関数が劇的に異なる場合は当てはまりませんが、同じである必要があるとする記述は言語にはありません。したがって、ローカル変数constを宣言すると、動作が少し異なる可能性があり、おそらくconstの動作が望ましいでしょう。パフォーマンスに影響する可能性があると考えることができる唯一の例は、マルチスレッドの状況です。非constメンバー関数へのアクセスには、すべてのリーダーの終了を待たなければならない書き込みロックの取得が含まれる場合がありますが、constバージョンには、1つのライターの終了を待つだけの場合があります。

したがって、組み込み型や一部の小さなクラスでは、引数がそれほど強くない場合でも、多くの状況でconstを選択するのに十分な理由があります。この場合、あまり考えられないルールを適用することをお勧めします。大きな利点がないいくつかの例外を設けるよりも、常にconstを優先することをお勧めします。

3
Tim

ローカルのプリミティブ変数の場合、関数をトレースするときの定数としてconstnessは非常に役立つと思います。 「this never changed」とすぐに通信し、状態の変化に関連する問題をデバッグしようとしている場合、定数として定義されているすべてのものがすぐに省略できます容疑者のリスト。プログラマーの意図を簡潔に伝えるのと同じくらい、防御型プログラミングにはそれほど価値があるとは思いませんでした。

コードをデバッグするときは、通常、状態の​​調査と、コードの各行で状態がどのように変化するかを調べるのに多くの時間を費やしています。 constはすぐに言います、「私を監視する必要はありません、私は変更するつもりはありません」だから私はそれが非常に貴重であると思います変更する必要がないローカルの場合は、constをより頻繁に使用することをお勧めします。

CとC++がめちゃくちゃになったのは、constnessがユーザー定義型とどのように相互作用するかでした。次のようなものがあれば:

// Does this cause side effects?
void do_something(const iterator first, const iterator last);

...アルゴリズムは範囲を変更しますか? constnessconstを変更するだけでポインタを変更できないので、ここではiteratorが回答する質問ではありません。 pointeeではありません。 firstlastをコピーして、ポインタと指示先の両方を変更できる場所にコピーするのも簡単です。その結果、言語の性質上、pointeeが変更されないように、完全に別個のconst_iteratorタイプが必要になります。

// This probably doesn't cause side effects.
void do_something(const_iterator first, const_iterator last);

同じケース:

// Some kind of handle.
class Foo
{
public:
    ...

    // Does this cause side effects?
    void some_func() const;

private:
    Bar* ptr;
};

const iteratorと同じシナリオ。 FooBarへのポインターを格納するので、some_funcを読み取り専用としてマークしても、指示先であるBarの内容を変更できます。ポインタが変更されないようにするだけです。

constがUDTメンバーのpointeesではなくpointersに適用されるという考えは、通常、懸念すべき副作用がある場合、それはポインターではなくpointeeに関するものです。 const iteratorを受け入れる関数は、ポインターに触れない限り、ポインターに対して何を行っても、そのパラメーターを介して副作用を引き起こすことはできませんが、constnessは逆の方法を適用します。

関数が関係している場合の関心の真の問題は、通常、それらが副作用を引き起こすかどうかであり、constは、これらの2つの言語では、それを明確に示して明確に示すことさえできませんでした。私はこの問題が解決されることを望みます。おそらく、関数が自身のスコープ外の変数に対して副作用を呼び出さないようにする新しいキーワードを使用します(副作用を引き起こす他の関数を呼び出すことができず、関数ローカルを定義できない)静的変数)。それは私の夢の安全機能の1つになります。それまでは、UDTレベルでconst-correctnessに煩わされることがなくなり、すべてに対して2つの異なるクラス(1つは読み取り専用クラス、1つは可変クラス)を作成しようとしました。節約するよりも多くの時間がかかります。

2
user204677