web-dev-qa-db-ja.com

ヘッダーの関数パラメーターとして使用される「プリミティブ」型の前にある「const」を削除する方が良いですか?

コードレビュープロセスで、同僚の1人が、ヘッダーの関数パラメーターとして使用される「プリミティブ型」の前の「定数」は無意味であると述べ、これらの「定数」を削除することを勧めました。そのような場合には、ソースファイルでのみ「const」を使用することを提案しました。プリミティブ型とは、「int」、「char」、「float」などの型を意味します。

以下は例です。

example.h

int ProcessScore(const int score);

example.cc

int ProcessScore(const int score) {
  // Do some calculation using score
  return some_value;
}

彼の提案は次のとおりです。

example.h

int ProcessScore(int score);  // const is removed here.

example.cc

int ProcessScore(const int score) {
  // Do some calculation using score
  return some_value;
}

しかし、私は多少混乱しています。通常、ユーザーはヘッダーのみを見るため、ヘッダーとソースファイルの間に矛盾がある場合、混乱が生じる可能性があります。

誰でもこれについてアドバイスをいただけますか?

47
chanwcom

allタイプ(プリミティブだけでなく)の場合、関数宣言のトップレベル const修飾子は無視されます。したがって、次の4つはすべて同じ関数を宣言しています。

void foo(int const i, int const j);
void foo(int i, int const j);
void foo(int const i, int j);
void foo(int i, int j);

ただし、関数内ではconst修飾子は無視されませんbody。そこでは、constの正確さに影響を与える可能性があります。しかし、それは関数の実装の詳細です。したがって、一般的なコンセンサスは次のとおりです。

  1. Constは宣言から除外します。混乱しているだけで、クライアントが関数を呼び出す方法には影響しません。

  2. コンパイラーがパラメーターの偶発的な変更をキャッチしたい場合は、定義にconstを残します

68
StoryTeller

Constを宣言し、constを使用しない関数パラメーターは、オーバーロード解決の場合と同じです。たとえば関数の例

_void f(int);
void f(const int);
_

同じであり、一緒に定義できませんでした。結果として、可能な重複を避けるために、パラメータの宣言でconstをまったく使用しない方が良いです。 (私はconst参照またはconstポインターについては話していません-const修飾子はトップレベルではないからです。)

これは、標準からの正確な引用です。

パラメータタイプのリストを作成した後、パラメータタイプを変更する最上位のcv-qualifiersは、関数タイプを作成するときに削除されます。変換されたパラメータータイプの結果リストと、省略記号または関数パラメーターパックの有無は、関数のparameter-type-listです。 [注:この変換は、パラメーターのタイプには影響しません。たとえば、int(*)(const int p, decltype(p)*)int(*)(int, const int*)は同じ型です。 —終了ノート]

関数定義でのconstの有用性は議論の余地があります-その背後にある理由は、ローカル変数を宣言するためにconstを使用すると同じです-コードを読んでいる他のプログラマーに示しますthis値は関数内で変更されません。

41
Artemy Vysotsky

コードレビューで指定された推奨事項に従ってください。

値の引数にconstを使用してもセマンティック値はありません—関数の実装にとって(潜在的に)意味があるだけです。

編集:明確にするために:関数のプロトタイプは、関数へのパブリックインターフェイスです。 constは、参照を変更しないことを保証します。

int a = 7;
do_something( a );

void do_something(       int& x );  // 'a' may be modified
void do_something( const int& x );  // I will not modify 'a'
void do_something(       int  x );  // no one cares what happens to x

constの使用はTMIに似ています— 'x'が変更されているかどうかにかかわらず、関数内を除いて重要ではありません。

edit2: StoryTeller's answer の情報もとても気に入っています

15
Dúthomhas

他の多くの人々が答えたように、APIの観点から、以下はすべて同等であり、オーバーロード解決に関しては同等です。

void foo( int );
void foo( const int );

しかし、より良い質問は、これがこのAPIのコンシューマーにセマンティックな意味を提供するかどうか、または実装の開発者から適切な動作を強制するかどうかです。

これを明示的に定義する明確に定義された開発者のコ​​ーディングガイドラインがなければ、constスカラー引数にはすぐに明らかな意味的意味はありません

コンシューマーから:const intは入力を変更しません。リテラルでも、別の変数(constまたはnon _constの両方)からでも可能です。

開発者から:const intは、変数(この場合は関数引数)のローカルコピーに制限を課します。これは単に引数を変更することを意味し、変数の別のコピーを取得して代わりに変更します。

値渡しの引数を受け取る関数を呼び出すと、呼び出された関数のスタックにその引数のコピーが作成されます。これにより、関数はスコープ全体の引数のローカルコピーを取得し、呼び出しに渡された元の入力に影響を与えることなく、変更、計算などに使用できます。事実上、これはその入力のローカル変数引数を提供します。

引数をconstとしてマークすると、このコピーを変更できないことを意味します。しかしnotは、開発者がそれをコピーしてこのコピーに変更を加えることを禁止します。これは最初からのコピーであるため、実装内からそれほど多くのことを強制することはありません。最終的には、消費者の観点から大きな違いはありません。

これは、参照渡しとは対照的です。この場合、int&への参照は、const int&と意味的に異なります。前者はその入力を変更できます。後者は、入力を監視することしかできません(実装がconst_castconst- nessを離れていない場合に限りますが、この可能性は無視できます)。したがって、参照のconst- nessには意味的な意味が含まれます。

パブリックAPIに存在する多くの利点はありません。 (imo)実装に不必要な制限を導入します。独創的な、意的な例-次のような単純な関数

void do_n_times( int n )
{
   while( n-- > 0 ) {
       // do something n times
   } 
}

不要なコピーを使用して記述する必要があります。

void do_n_times( const int n )
{
    auto n_copy = n;
    while( n_copy-- > 0 ) {
        // do something n times
    }
}

constスカラーがパブリックAPIで使用されているかどうかに関係なく、重要なことの1つは、設計でconsistentであることです。 APIがconstスカラー引数の使用と非constスカラーの使用との間でランダムに切り替わると、消費者にとって暗黙の意味があるかどうかについて混乱を招く可能性があります。

TL; DR:constパブリックAPIのスカラー型は、ドメインの独自のガイドラインで明示的に定義されていない限り、セマンティックな意味を伝えません。

7
Human-Compiler