12

C++ Primer 第 5 版から、次のように書かれています。

int f(int){ /* can write to parameter */}
int f(const int){ /* cannot write to parameter */}

2 つの機能は区別できません。しかし、ご存じのとおり、2 つの関数はパラメーターを更新する方法が大きく異なります。

誰かが私に説明できますか?


編集
私は自分の質問をうまく解釈しなかったと思います。私が本当に気にしているのは、「パラメーターを記述できるかどうか」に関して実際に異なるため、C++がこれら2つの関数を異なる関数として同時に許可しない理由です。直感的に、そうあるべきです!


EDIT値渡し
の性質は、実際には引数値をパラメータ値にコピーして渡すことです。コピーされた値がアドレスである参照ポインターの場合でも。呼び出し元の観点からは、constまたはnon-constが関数に渡されるかどうかは、パラメーターにコピーされる値 (およびもちろん型) に影響しません。オブジェクトをコピーするときは、トップレベルの constローレベルの const の違いが重要になります。より具体的には、トップレベルの const (ローレベルの constの場合ではない)
) は、コピーがコピーされたオブジェクトに影響を与えないため、オブジェクトをコピーするときに無視されます。コピー先またはコピー元のオブジェクトがconstであるかどうかは重要ではありません。
したがって、呼び出し元にとって、それらを区別する必要はありません。おそらく、関数の観点からは、最上位の constパラメーターはインターフェイスや関数の機能に影響を与えません。この 2 つの関数は、実際には同じことを実現します。なぜわざわざ 2 つのコピーを実装するのでしょうか。

4

7 に答える 7

11

この2つの機能は「パラメータを書けるか書けないか」が全然違うので、別の機能として同時に許してください。直感的に、そうあるべきです!

関数のオーバーロードは、呼び出し元が提供するパラメーターに基づいています。constここで、呼び出し元が値または非値を提供する可能性があることは事実ですconstが、論理的には、呼び出された関数が提供する機能に違いはありません。検討:

f(3);
int x = 1 + 2;
f(x);

f()これらの状況のそれぞれで異なることを行うと、非常に混乱します! このコード呼び出しのプログラマーはf()、プログラムを無効にすることなく、パラメーターを渡す変数を自由に追加または削除して、同一の動作を合理的に期待できます。この安全で健全な動作は、例外を正当化したい出発点であり、実際に 1 つある -関数の ala がオーバーロードされたときに動作を変えることができる:

void f(const int&) { ... }
void f(int&) { ... }

だから、これはあなたが直感的でないと思うものだと思います.C++は、参照よりも非参照に対してより多くの「安全性」(単一の実装のみをサポートすることによって一貫した動作を強制する)を提供します.

私が考えることができる理由は次のとおりです。

  • const&そのため、非パラメータの寿命が長くなることをプログラマが知っている場合、プログラマは最適な実装を選択できます。たとえば、以下のコードでは、T内のメンバーへの参照を返す方が高速な場合がありますFが、 がF一時的な場合 (コンパイラが に一致する場合const F&)、値による戻りが必要です。呼び出し元は、返された参照がパラメーターが存在する限り有効であることを認識する必要があるため、これは依然としてかなり危険です。
    T f(const F&);
    T&f(F&); // より適切な場合、戻り値の型は const& である可能性があります
  • const次のような関数呼び出しによる -nessなどの修飾子の伝播:
    const T& f(const F&);
    T&f(F&);

ここでは、型のいくつかの (おそらくFmember-) 変数が、呼び出されたときのパラメーターの -ness に基づいて、または非-Tとして公開されています。このタイプのインターフェースは、クラスを非メンバー関数で拡張したい場合 (クラスのミニマリストを維持するため、または多くのクラスで使用可能なテンプレート/アルゴを作成する場合) に選択できますが、考え方は のようなメンバー関数に似ています。ベクトル以外では許可されますが、ベクトルでは許可されません。constconstconstf()constvector::operator[]()v[0] = 3constconst

値が値によって受け入れられると、関数が返されるときにスコープ外になるため、パラメーターの一部への参照を返し、その修飾子を伝達したいという有効なシナリオはありません。

必要な動作をハッキングする

参照の規則があれば、それらを使用して必要な種類の動作を取得できます-非 const-reference パラメーターを誤って変更しないように注意する必要があるため、次のようなプラクティスを採用することをお勧めします非 const パラメータ:

T f(F& x_ref)
{
    F x = x_ref;  // or const F is you won't modify it
    ...use x for safety...
}

再コンパイルの影響

なぜ言語が値渡しパラメータの -ness に基づくオーバーロードを禁止するのかという問題とは別に、宣言と定義で -ness のconst一貫性を主張しないのはなぜかという問題があります。const

f(const int)/ f(int)... の場合、ヘッダー ファイルで関数を宣言している場合は、実装ファイルの後の定義に修飾子が含まれている場合でも、修飾子を含めないことをお勧めしますconst。これは、メンテナンス中にプログラマーが修飾子を削除したい場合があるためです...ヘッダーから修飾子を削除すると、クライアントコードの無意味な再コンパイルがトリガーされる可能性があるため、同期を維持することを主張しない方がよいでしょう-実際、それがコンパイラがそうしない理由です'それらが異なる場合、エラーが発生します。関数定義で追加または削除するだけの場合const、コードのリーダーが関数の動作を分析するときに constness を気にする可能性がある実装に近くなります。ヘッダーと実装ファイルの両方にある場合const、プログラマーはそれを非constconstクライアントの再コンパイルを避けるためにヘッダーを更新しないことを忘れるか、決定しない場合、プログラマーが現在の実装コードを分析しようとするときにヘッダーのバージョンを念頭に置いて間違ったコードを作成する可能性があるため、逆の場合よりも危険です。関数の動作についての推論。これはすべて非常に微妙な保守上の問題であり、実際には商用プログラミングにのみ関連しますが、それconstがインターフェイスで使用しないというガイドラインの基礎となっています。さらに、インターフェイスから省略した方が簡潔です。これは、API を読み取るクライアント プログラマーにとってより適切です。

于 2013-06-20T08:35:00.457 に答える
3

「The C++ Programming Language」の第 4 版で、Bjarne Stroustrup は次のように書いています (§12.1.3):

残念ながら、C との互換性を維持するために、const は引数型の最上位レベルでは無視されます。たとえば、これは同じ関数の 2 つの宣言です。

void f(int);
void f(const int);

したがって、他のいくつかの回答とは反対に、C++のこのルールは、2つの関数の区別がつかない、または他の同様の理由から選択されたのではなく、最適ではない解決策として選択されたようです。互換性。

実際、Dプログラミング言語では、これら 2 つのオーバーロードを使用できます。ただし、この質問に対する他の回答が示唆するものとは反対に、関数がリテラルで呼び出される場合は、非 const オーバーロードが優先されます。

void f(int);
void f(const int);

f(42); // calls void f(int);

もちろん、オーバーロードに同等のセマンティクスを提供する必要がありますが、それはこのオーバーロード シナリオに固有のものではなく、ほとんど見分けがつかないオーバーロード関数を使用します。

于 2013-12-01T16:10:42.300 に答える