関数パラメーターの 'const'の使用


397

どこまで行くのconstconst必要なときに関数を作成するだけですか、それとも、すべてを独り占めしてどこでも使用しますか?たとえば、単一のブール型パラメーターを取る単純なミューテーターを想像してください。

void SetValue(const bool b) { my_val_ = b; }

それはconst実際に役に立ちますか?個人的には、パラメーターを含めて幅広く使用することにしましたが、この場合、それは価値があるのでしょうか。

またconst、関数宣言でパラメーターを省略できるが、関数定義に含めることができることにも驚いた。たとえば、

.hファイル

void func(int n, long l);

.cppファイル

void func(const int n, const long l)

これには理由がありますか?それは私には少し珍しいようです。


同意しません。.hファイルにもconst定義が必要です。そうでない場合、constパラメーターが関数に渡されると、.hファイルのプロトタイプにconst定義がないため、コンパイラーはエラーを生成します。
selwyn 2008

10
同意する。:-)(最後のコメントではなく質問です!)関数の本体で値を変更しない場合、これは愚かな==または=バグを停止するのに役立ちます。両方にconstを入れないでください(ifそれは値によって渡されます、そうでなければあなたはそうしなければなりません)それについての議論に入るほど深刻ではありません!
Chris Huang-Leaver

19
@selwyn:ただし、const intを関数に渡しても、それは(参照ではないため)コピーされるため、const-nessは関係ありません。
2009年

1
この質問でも同じ議論が起こっています:stackoverflow.com/questions/1554750/…–
部分的に

5
私はこの投稿が2年前のものであることを理解していますが、新しいプログラマーとして、私はこの質問そのものに疑問を感じ、この会話に偶然出会いました。私の意見では、関数が値を変更してはならない場合、その参照であれ、値/オブジェクトのコピーであれ、constでなければなりません。より安全で、自己文書化されており、よりデバッグしやすくなっています。1つのステートメントを持つ最も単純な関数でも、constを使用します。

回答:


187

その理由は、パラメーターのconstはデータのコピーを処理するため、関数内でローカルにのみ適用されるためです。つまり、関数のシグネチャはとにかく同じです。しかし、これをたくさん行うのはおそらく悪いスタイルです。

個人的には、参照パラメーターとポインターパラメーターを除いて、constを使用しない傾向があります。コピーされたオブジェクトの場合、関数内の意図を示すため、安全である可能性はありますが、実際には問題になりません。それは本当に判断の呼びかけです。私は何かをループするときにconst_iteratorを使用する傾向がありますが、それを変更するつもりはないので、参照型のconstの正確性が厳密に維持されている限り、それぞれのconst_iteratorを推測します。


57
「悪いスタイル」の部分には同意できません。削除const関数のプロトタイプからすると、ドロップすることを決定した場合は、ヘッダファイルを変更する必要はありませんという利点があるconst以降の実装部からを。
のMichałGórny

4
「私は個人的に、参照パラメーターとポインターパラメーターを除いてconstを使用しない傾向があります。」多分あなたは「私は関数宣言で余計な修飾子を使わない傾向があるがconst、それが有用な違いを生むところに使うこと」を明確にすべきです。
Deduplicator

3
私はこの答えに同意しません。私は他の方法で学習し、const可能な限りパラメーターをマークします。より表現力があります。私は他の誰かのコードを読んだとき、私は彼らがなど、マジックナンバーのようなものと一緒に自分のコードを書くコメント、および適切なポインタの使用に入れどのくらいのケアを判断するために、このような小さな指標を使う
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }これを試して。もちろん、++aはそこには何もしませんが、長期間にわたって複数のプログラマーによって書かれた長い関数の中で見つけることができます。をint getDouble( const int a ){ //... }見つけるとコンパイルエラーが発生するように書くことを強くお勧めします++a;
dom_beau 2017年

3
誰がどの情報を必要とするかはすべて問題です。パラメータを値で指定するので、呼び出し側はそれを使用して(内部で)何を行っているかを知る必要がありません。だからclass Foo { int multiply(int a, int b) const; }あなたのヘッダーに書いてください。あなたの実装では、変更しないことを約束できるように気をつけているので、ここでは理にかなっています。(補足:呼び出し元と実装の両方が、関数がそのオブジェクトを変更しないという事実、つまりその宣言の最後のconstにabint Foo::multiply(const int a, const int b) const { }Foo
注意を払います

415

「呼び出し元のオブジェクトを変更しないため、引数が値で渡される場合、constは無意味です。」

違う。

それはあなたのコードとあなたの仮定を自己文書化することです。

あなたのコードがそれに取り組んでいる多くの人々を持っていて、そしてあなたの関数が重要なものであるなら、あなたはあなたができる限りすべてのものを「const」とマークするべきです。産業用強度のコードを作成するときは、同僚ができる限りの方法であなたを取得しようとしている精神病質者であると常に想定する必要があります(特に、将来自分自身になることが多いため)。

その上、誰かが以前に述べたように、それはコンパイラが物事を少し最適化するのを助けるかもしれません(それは長い道のりですが)。


41
完全に同意する。それはすべて、人々とのコミュニケーションと、変数を使用して実行できることを実行すべきものに制限することです。
Len Holgate、

19
私はこれに反対票を投じました。値の引数による単純な受け渡しに適用する場合、constで表示しようとしているものを希釈すると思います。
tonylo 2008

26
私はこれに投票しました。パラメーター 'const'を宣言すると、パラメーターに意味情報が追加されます。彼らは、コードの元の作者が意図したことを強調しており、これは時間が経つにつれてコードのメンテナンスに役立ちます。
リチャードコーデン

13
@tonylo:あなたは誤解しています。これは、コードのブロック内でローカル変数をconstとしてマークすることです(たまたま関数です)。ローカル変数についても同じようにします。これは、const-correctであるAPIを持つことと直交しており、これも実際に重要です。
rlerallut 2008

56
また、関数のバグをキャッチできます。パラメーターを変更してはならないことがわかっている場合、パラメーターをconstと宣言すると、誤って変更した場合にコンパイラーから通知されます。
エイドリアン

156

時々(あまりに頻繁に!)他の人のC ++コードを解く必要があります。そして、他の誰かの C ++コードがほぼ完全に混乱していることは誰もが知っています:)ローカルデータフローを解読するために最初に行うことは、コンパイラが吠え始めるまで、すべての変数定義にconstを入れます。これはconst修飾値の引数も意味します。これらは呼び出し元によって初期化されたファンシーなローカル変数だからです。

ああ、私は変数がデフォルトでconstであり、非const変数にはmutableが必要だったと思います:)


4
「変数がデフォルトでconstであることを望みます」-oxymoron ?? 8-)真剣に、すべてを「解釈」することで、コードのもつれを解くのにどのように役立ちますか?元の作家が定数と思われる引数を変更した場合、変数が定数であると想定されていたことがどうやってわかりますか?さらに、(非引数)変数の大部分は...変数であることを意図しています。そのため、プロセスを開始した直後にコンパイラは壊れるはずです。
ysap 2017年

8
@ ysap、1。constをできるだけマークすると、動いている部分と動いていない部分を確認できます。私の経験では、多くの地元民は事実上のconstであり、その逆ではありません。2.「const変数」/「Immutable変数」はoxymoronのように聞こえるかもしれませんが、関数型言語や一部の非関数型言語では標準的な方法です。たとえば、Rustを参照してください:doc.rust-lang.org/book/variable-bindings.html
Constantin

1
また、一部の状況ではC ++でも標準になっています。たとえば、ラムダ[x](){return ++x;}はエラーです。こちらを
anatolyg 2017年

10
変数はconstRustではデフォルトで" "です:)
フェニックス

変数は、変数を変化させるために必ずしも割り当て可能である必要はありません。それらが初期化される値は、実行時にも変化します。
スフィンクス

80

次の2行は機能的に同等です。

int foo (int a);
int foo (const int a);

明らかに、それが2番目の方法で定義されている場合a、本体で変更することはできませんがfoo、外部との違いはありません。

どこconst本当に参照またはポインタパラメータと便利ですしています:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

これが言うことは、fooは大きなパラメーター(おそらくギガバイトのサイズのデータ​​構造)をコピーせずに取ることができるということです。また、それは呼び出し側に「Fooはそのパラメーターの内容を変更しません*」と言います。const参照を渡すことにより、コンパイラーは特定のパフォーマンス決定を行うこともできます。

*:const-nessを捨てない限り、それは別の投稿です。


3
それはこの質問についてではありません。もちろん、参照またはポイントされた引数については、constを使用することをお勧めします(参照またはポイントされた値が変更されていない場合)。ポインターの例ではconstであるパラメーターではないことに注意してください。それは、パラメーターが指すものです。
tml

> const参照を渡すことにより、コンパイラーは特定のパフォーマンス決定を行うこともできます。古典的な誤り-コンパイラーはconst-nessを自分で決定する必要があります。ポインターのエイリアシングとconst_castのおかげでconstキーワードは役に立ちません
jheriko

73

余分な余分なconstは、APIの観点からは悪いです。

値によって渡される組み込み型パラメーターの余分なconstをコードに追加すると、APIがすっきりしますが、呼び出し元またはAPIユーザーに意味のある約束はありません(実装を妨げるだけです)。

APIで不要な「const」が多すぎると「泣いているオオカミ」のようになり、最終的には「const」を無視し始めます。なぜなら、それはあちこちにあり、ほとんどの場合何も意味しないからです。

APIの追加のconstに対する「reductio ad absurdum」引数は、これらの最初の2つの点に適しています。より多くのconstパラメータが適切である場合、constを持つことができるすべての引数にはconstが必要です。実際、本当にそれが良ければ、constをパラメーターのデフォルトにして、パラメーターを変更する場合にのみ「可変」のようなキーワードを設定することをお勧めします。

だからできる限りconstを入れてみよう:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

上記のコード行を検討してください。宣言がすっきりし、長くて読みにくいだけでなく、4つの 'const'キーワードのうち3つは、APIユーザーが安全に無視できます。ただし、「const」を余分に使用すると、2行目が危険になる可能性があります。

どうして?

最初のパラメーターをすぐに読み間違えると、char * const buffer渡されたデータバッファーのメモリは変更されないと思われるかもしれませんが、これは正しくありません。過剰な 'const'は、スキャンしたり、すぐに誤解したりすると、APIに関する危険で誤った想定につながる可能性があります


コード実装の観点からも、余分なconstは良くありません。

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

FLEXIBLE_IMPLEMENTATIONがtrueでない場合、APIは以下の最初の方法で関数を実装しないことを「約束」します。

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

それは非常に愚かな約束です。呼び出し側にまったくメリットがなく、実装を制限するだけの約束をする必要があるのはなぜですか?

これらは両方とも、同じ機能の完全に有効な実装ですが、行ったすべての作業は、不必要に背中の後ろに縛られています。

さらに、それは簡単に(そして法的に回避される)非常に浅い約束です。

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

見てください、とにかく約束しましたが、ラッパー関数を使用するだけです。それは、悪者が映画の誰かを殺さないと約束し、代わりに彼のヘンチマンに彼らを殺すように命じるようなものです。

これらの余分なconstは、映画の悪者からの約束に過ぎません。


しかし、嘘をつく能力はさらに悪化します。

偽のconstを使用することで、ヘッダー(宣言)とコード(定義)のconstを不一致にできることを悟りました。constを支持する支持者は、constを定義にのみ入れることができるため、これは良いことだと主張しています。

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

ただし、その逆は真です...偽のconstを宣言にのみ入れて、定義では無視できます。これは、APIの余分なconstを恐ろしいものにして恐ろしい嘘にするだけです。次の例を参照してください。

class foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

余分なconstが実際に行うことは、変数を変更したり、非const参照で変数を渡したりしたいときに、実装者のコードを読みにくくすることです。

この例を見てください。どちらがより読みやすいですか?2番目の関数に追加の変数がある唯一の理由は、一部のAPI設計者が余分なconstをスローしたためであることは明らかですか?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

うまくいけば、ここで何かを学びました。余分なconstは、APIが散らかった目障り、迷惑なナグ、浅くて意味のない約束、不必要な妨害であり、非常に危険なミスにつながることがあります。


9
なぜ反対票?反対票に短いコメントを残すと、はるかに役立ちます。
Adisak 2013

7
const引数を使用するポイントは、マークされた行を失敗させることです(plist = pnext)。関数の引数を不変に保つことは、妥当な安全対策です。私はあなたが彼らが関数宣言に悪い(彼らは余分なものであるため)のであるが、それらは実装ブロックでそれらの目的を果たすことができるという点に同意します。
touko

23
@Adisak回答自体には問題はありませんが、コメントから重要な点が欠けているようです。関数の定義/実装がありません唯一の機能であるAPIの一部宣言。あなたが言ったように、constパラメーターで関数を宣言することは無意味であり、混乱を追加します。ただし、APIのユーザーは、その実装を確認する必要がない場合があります。一方、実装者は、明確にするためにのみ関数定義の一部のパラメーターをconst修飾することを決定する場合がありますが、これはまったく問題ありません。
jw013 2013

17
@ jw013が正しい、void foo(int)void foo(const int)まったく同じ機能ではなく、オーバーロードされています。ideone.com/npN4W4 ideone.com/tZav9Rここでのconstは、関数本体の実装の詳細であり、オーバーロードの解決には影響しません。より安全で簡潔なAPIのために、constを宣言から除外しますが、コピーされた値を変更しない場合は、constを定義に含めます。
Oktalist 2014

3
@Adisak私はこれが古いことを知っていますが、パブリックAPIの正しい使い方はその逆になると思います。そうすることで、内部の作業をしている開発者は、pi++想定されていない場合などのミスを犯しません。
CoffeeandCode

39

C ++では、constがデフォルトであるはずです。このような :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

8
まさに私の考え。
コンスタンティン

24
Cとの互換性は、少なくともC ++を設計する人々にとって、これを考慮することさえも重要です。

4
興味深いことに、私はそのことを考えたことはありませんでした。
Dan

6
同様に、unsignedC ++ではデフォルトでした。このように:int i = 5; // i is unsignedsigned int i = 5; // i is signed
hkBattousai 2015

25

私が生計を立てるためにC ++をコーディングしたとき、私は可能な限りすべてを解釈しました。constを使用することは、コンパイラーがあなたを助けるのに役立つ素晴らしい方法です。たとえば、メソッドの戻り値を構成すると、次のようなタイプミスを防ぐことができます。

foo() = 42

あなたが意味したとき:

foo() == 42

foo()が非const参照を返すように定義されている場合:

int& foo() { /* ... */ }

コンパイラーは、関数呼び出しによって返される匿名の一時変数に値を割り当てることができます。constにする:

const int& foo() { /* ... */ }

この可能性を排除します。


6
それはどのコンパイラで機能しましたか?コンパイルの試行中にGCCがエラーを表示するfoo() = 42:エラー:代入の左オペランドとしてlvalueが必要
gavrie

これは正しくありません。foo()= 42は2 = 3と同じです。つまり、コンパイラエラーです。そしてconstを返すことは完全に無意味です。組み込み型に対しては何もしません。
Josh、

2
私はこのconstの使用法に遭遇し、私はあなたに言うことができます、結局それは利点よりもはるかに面倒を生み出します。ヒント:はとconst int foo()はタイプが異なるためint foo()、関数ポインター、シグナル/スロットシステム、またはboost :: bindなどを使用している場合、大きな問題が発生します。
Mephane

2
参照の戻り値を含めるようにコードを修正しました。
Avdi、2011年

戻り値の最適化によりconst int& foo()、と実質的に同じではありませんint foo()か?
ザンティエ2014年

15

comp.lang.c ++。moderatedの古い「今週の達人」の記事には、このトピックに関する良い議論があります

対応するGOTWの記事は、こちらのHerb SutterのWebサイトにあります


1
ハーブサッターは本当に賢い人です:-)間違いなく読む価値があり、私は彼のすべての点に同意します。
Adisak

2
良い記事ですが、私は彼の議論については同意しません。それらは変数のようなものなので、私もそれらをconstにします。そして、誰かが私の引数に変更を加えてほしくありません。
QBziZ

9

私はあなたの値パラメータをconstと言います。

このバグの多い関数を考えてみましょう:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

数値パラメーターがconstの場合、コンパイラーは停止し、バグを警告します。


2
別の方法はif(0 == number)... else ...;
Johannes Schaub-litb 2008年

5
@ ChrisHuang-Leaver恐ろしいです、ヨーダのように話すとあなたはそうします:stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wallは-Wparenthesesを提供します。これは、意図したとおりの場合は「if((number = 0))」にする必要があります。これはヨーダの代わりにうまく機能します。
ジェットスキーSタイプ2018

8

[in]データのみであり、関数によって変更されない参照(またはポインター)である関数パラメーターにconstを使用します。つまり、参照を使用する目的がデータのコピーを回避し、渡されたパラメーターを変更できないようにする場合です。

例でブールbパラメータにconstを指定すると、実装に制約が適用されるだけで、クラスのインターフェースには影響しません(ただし、パラメータを変更しないことをお勧めします)。

関数シグネチャ

void foo(int a);

そして

void foo(const int a);

あなたの.cと.hを説明する同じです

アサフ


6

->*or .*演算子を使用する場合は、必須です。

それはあなたがのようなものを書くのを防ぎます

void foo(Bar *p) { if (++p->*member > 0) { ... } }

これは私が今やろうとしていることで、おそらくあなたが意図したことをしていないでしょう。

言いたかったのは

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

私は入れていた場合とconstの間にBar *p、コンパイラは私のことを語っていると思います。


4
演算子の優先順位に関する参照をすぐに確認するときは、その数の演算子を混合しようとすると(まだ100%がわからない場合)、IMOは問題になりません。
mk12 2012

5

ああ、タフなもの。一方では、宣言はコントラクトであり、値でconst引数を渡すことは実際には意味がありません。一方、関数の実装を見ると、引数定数を宣言すると、コンパイラーが最適化する機会が増えます。


5

呼び出し元のオブジェクトを変更しないため、引数が値で渡される場合、constは無意味です。

関数の目的が渡された値を変更することでない限り、参照渡しではconstが推奨されます。

最後に、現在のオブジェクト(this)を変更しない関数は、constとして宣言できます。以下に例を示します。

int SomeClass::GetValue() const {return m_internalValue;}

これは、この呼び出しが適用されるオブジェクトを変更しないという約束です。つまり、次のように呼び出すことができます。

const SomeClass* pSomeClass;
pSomeClass->GetValue();

関数がconstでない場合、これはコンパイラの警告になります。


5

値パラメーターを「const」とマークすることは、間違いなく主観的なことです。

ただし、実際には、例のように、値パラメーターをconstとマークすることを好みます。

void func(const int n, const long l) { /* ... */ }

私にとっての値は、関数のパラメーター値が関数によって変更されないことを明確に示しています。開始時と終了時で同じ値になります。私にとって、それは非常に機能的なプログラミングのようなスタイルを維持することの一部です。

短い関数の場合、引数が関数によって変更されないことは通常かなり明らかであるため、「const」をそこに置くことは間違いなく時間/空間の無駄です。

ただし、より大きな関数の場合、これは実装ドキュメントの形式であり、コンパイラーによって強制されます。

「n」と「l」を使用して計算を行うと、一方または両方が変更された場所を逃したため、別の結果を取得することを恐れずにその計算をリファクタリング/移動できます。

これは実装の詳細なので、実装で使用するのと同じ名前の関数パラメーターを宣言する必要がないのと同じように、ヘッダーで値パラメーターconstを宣言する必要はありません。


4

これは有効な引数ではないかもしれません。しかし、関数コンパイラ内のconst変数の値をインクリメントすると、エラーが発生します。 " error:increment of read-only parameter "。つまり、関数内で変数を誤って変更することを防ぐ方法としてconstキーワードを使用できるということです(これは、/読み取り専用ではありません)。したがって、コンパイル時に誤ってそれを行った場合、コンパイラーはそれを知らせます。このプロジェクトに取り組んでいるのがあなただけではない場合、これは特に重要です。


3

可能な限りconstを使用する傾向があります。(またはターゲット言語に適した他のキーワード。)これは、コンパイラーが他の方法では行うことができない追加の最適化を行うことができるためです。これらの最適化が何であるか私にはわからないので、ばかげているように見える場所でも、常にそれを行います。

私が知っている限りでは、コンパイラーはconst値パラメーターを非常によく見て、「この関数はとにかくそれを変更していないので、参照渡ししていくつかのクロックサイクルを節約できる」と言うかもしれません。関数のシグネチャを変更するので、そのようなことをすることはないと思いますが、それは重要です。多分それはいくつかの異なるスタック操作か何かをします...ポイントは、私は知りませんが、コンパイラーが私を恥ずかしくさせるだけであるよりも賢くしようとすることを知っています。

C ++には、const-correctnessの概念を備えた追加の手荷物があるため、さらに重要になります。


場合によっては役立つかもしれませんが、最適化を促進する可能性はの利点として劇的に誇張されていると思いconstます。むしろ、それは実装内の意図を述べ、後でシンコをキャッチすることの問題です(誤っていたローカル変数が誤ってインクリメントされていましたが、そうではなかったためですconst)。並行して、関数をインライン化できるという意味で、コンパイラーが関数のシグネチャを変更することは非常に歓迎され、いったんインライン化されるとその機能全体を変更できることも付け加えておきます。参照の追加または削除、「変数」リテラルの作成などはすべてas-ifルール内にあります
underscore_d

3

1.私の評価に基づく最良の回答:

@Adisakの回答は、私の評価に基づくここでの最良の回答です。注こともあるので、この答えは最良の部分であることを最もよくバックアップされた実際のコード例で、音とよく考え抜かれたロジックを使用してに加えて。

2.自分の言葉(ベストアンサーに同意):

  1. 値渡しの場合、を追加してもメリットはありませんconst。それがするすべては:
    1. ソースコードの入力パラメーターを変更するたびにコピーを作成する必要があるように実装者を制限します(渡されるものが値渡しであるため、コピーは既にコピーであるため、変更による副作用はありません)。そして頻繁に、値によって渡される入力パラメーターを変更することは、関数を実装するために使用されるため、constどこにでも追加すると、これを妨げる可能性があります。
    2. そして、const不必要にコードを追加すると、constどこにでもsが散らばって、const安全なコードを作成するために本当に必要なs から注目が集まります。
  2. ただし、ポインターまたは参照を処理するconst場合は、必要に応じて非常に重要であり、使用する必要あります。これにより、関数の外部で永続的な変更による望ましくない副作用が防止されるため、parmが入力のみの場合にすべての単一のポインターまたは参照使用constする必要があります。出力ではありません。参照またはポインターによって渡されるパラメーターでconst のみ使用することには、どのパラメーターがポインターまたは参照であるかを明確にするという追加の利点があります。気を付けて、「気をつけて!そのconst隣にあるパラメータは参照またはポインタです!」と言うのはもう1つです。
  3. 上記で説明したのは、私が携わった専門のソフトウェア組織で達成されたコンセンサスであり、ベストプラクティスと見なされてきました。時には、ルールが厳格なされています:「今までに値で渡されたパラメータにconstとして使用しますが、常に彼らは入力のみであれば、参照やポインタで渡されたパラメータにそれを使用しないでください。」

3. Googleの言葉(私とベストアンサーに同意):

(「Google C ++スタイルガイド」から)

値で渡される関数パラメーターの場合、constは呼び出し元に影響を与えないため、関数宣言では推奨されません。TotW#109を参照してください。

ローカル変数にconstを使用することは推奨も推奨もされません。

出典:Google C ++スタイルガイドの「Use of const」セクション:https : //google.github.io/styleguide/cppguide.html#Use_of_const。これは実際には非常に貴重なセクションなので、セクション全体をお読みください。

「TotW#109」は「今週のヒント#109:const関数宣言で意味がある」の略であり、参考としても役立つことに注意してください。それは何をすべきかについてより情報的で規範的ではありません、そしてコンテキストに基づいて直前に引用されたGoogle C ++スタイルガイドルールのに来constましたが、それが提供した明確さの結果として、const直前に引用されたルールがGoogle C ++に追加されましたスタイルガイド。

また、ここではGoogle C ++スタイルガイドを引用していますが、これは私の立場を擁護するためのものですが、常にガイドに従うことや、ガイドに従うことをお勧めするわけではありません。彼らはお勧めのもののいくつかは、以下のような、奇妙な単なるある彼らのkDaysInAWeek「定数名」のためのスタイルの命名規則ただし、それでもなお、世界で最も成功して影響力のある技術およびソフトウェア企業の1つが、@ Adisakのような他の企業と同じ正当化を使用して、この問題に関する私たちの視点をバックアップする場合を指摘することは、依然として有用で適切です。

4. Clangのリンターにはclang-tidy、このためのオプションがいくつかあります。

A. Clangのリンターにclang-tidyは、ここ説明するオプションがありreadability-avoid-const-params-in-decls値渡しの関数パラメーターに使用ないコードベースでの強制をサポートすることにも注意してくださいconst

関数宣言に最上位の定数であるパラメーターがあるかどうかを確認します。

宣言内のconst値は関数のシグネチャに影響を与えないため、そこに配置しないでください。

例:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

そして、ここに私が完全性と明確性のために私が追加している他の2つの例があります。

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B.また、このオプションがあります:readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5.問題についてスタイルガイドをどのように表現するかについての実際的なアプローチ:

これをコピーしてスタイルガイドに貼り付けます。

[コピー/貼り付け開始]

  1. const内容(それらが指すもの)を変更しないことを意図している場合は、参照またはポインターによって渡される関数パラメーターで常に使用します。このようにすると、参照またはポインターによって渡される変数が変更されると予想される場合、欠落するため、それが明らかになりますconst。この使用例constでは、関数外の偶発的な副作用を防止します。
  2. 値によって渡される関数パラメーターでの使用はお勧めません。これは、呼び出し元には影響がないconstためconstです。関数内で変数が変更されても、関数の外部で副作用は発生しません。その他の正当化と洞察については、次のリソースを参照してください。
    1. 「Google C ++スタイルガイド」「constの使用」セクション
    2. 「今週のヒント#109:const関数宣言で意味がある」
    3. Adisakの「関数パラメーターの 'const'の使用」に関するスタックオーバーフローの回答
  3. 絶対に使用しないでくださいトップレベルをconst[すなわち:constパラメータの値によって渡される ]で関数のパラメータで定義されない宣言(およびコピーへ/無意味を貼り付けないように注意してくださいconst。)それはそれは視覚的なノイズであり、無意味とコンパイラによって無視されます、そしてそれは読者を誤解させる可能性があります」(https://abseil.io/tips/109、強調を追加)。
    1. constコンパイルに影響を与える唯一の修飾子は、ヘッダーファイルの関数(メソッド)宣言などの関数の前方宣言の修飾子ではなく、関数定義に配置された修飾子です。
  4. 関数から返される値トップレベルのconst[ 値で渡されるconst変数]を使用しないでください
  5. const関数によって返されるポインタまたは参照での使用は、便利な場合があるので、インプリメンタ次第です。
  6. TODO:以下のclang-tidyオプションを使用して上記の一部を強制します。
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

const上記のルールを示すためのコード例を以下に示します。

constパラメータの例:(
一部はここから借りています

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

const戻り値の型の例:(
一部はここから借りられます

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[コピー/貼り付け終了]


2

あなたが言及した場合、それはあなたのAPIの呼び出し元に影響を与えません、それがそれが一般的に行われていない理由です(そしてヘッダーでは必要ありません)。関数の実装にのみ影響します。

これは特に悪いことではありませんが、APIに影響を与えず、タイピングを追加するため、メリットはそれほど大きくありません。そのため、通常は行われません。


2

値を渡すパラメーターにconstを使用しません。呼び出し元は、パラメーターを変更するかどうかを気にしません。これは実装の詳細です。

本当に重要なのは、インスタンスを変更しない場合にメソッドをconstとしてマークすることです。そうしないと、const_cast <>が大量に発生したり、constとマークされているはずの他のメソッドを呼び出すため、メソッドconstをマークしたり、多くのコードを変更する必要があることに気づく場合があります。

また、変更する必要がない場合は、ローカル変数constをマークする傾向があります。「可動部分」を識別しやすくすることで、コードが理解しやすくなると思います。



2

私はできる限りconstを使用します。パラメータの定数は、値を変更してはならないことを意味します。これは、参照渡しする場合に特に役立ちます。関数のconstは、関数がクラスのメンバーを変更してはならないことを宣言します。


2

要約する:

  • 「通常constの値渡しは役に立たず、せいぜい誤解を招くだけです。」GOTW006から
  • ただし、変数の場合と同様に、.cppに追加できます。
  • 標準ライブラリはconstを使用しないことに注意してください。例えばstd::vector::at(size_type pos)。標準ライブラリにとって十分なものは私にとっては良いことです。

2
「標準ライブラリに十分満足できることは私にとって良いことだ」とは必ずしも正しくありません。たとえば、標準ライブラリは_Tmpいつものように醜い変数名を使用します-これは必要ありません(実際には使用できません)。
anatolyg 2017

1
@anatolygこれは実装の詳細です
Fernando Pelliccioni '20

2
引数リストの変数名とconst修飾型はどちらも実装の詳細です。私が言いたいのは、標準ライブラリの実装は時々良くないということです。時には、あなたはもっと上手にできる(そしてそうすべきです)標準ライブラリのコードが作成されたのは10年前ですか。5年前(それのいくつかの最新の部分)?今日より良いコードを書くことができます。
anatolyg 2017

1

パラメーターが値で渡され(参照ではない)場合、通常、パラメーターがconstとして宣言されているかどうかに大きな違いはありません(参照メンバーが含まれていない限り、組み込み型の問題ではありません)。パラメータが参照またはポインタの場合、通常はポインタ自体ではなく、参照/参照されているメモリを保護する方が適切です(参照自体をconstにすることはできません。レフリーを変更できないため、それほど重要ではありません)。 。constとしてできる限りすべてを保護することをお勧めします。パラメータが単なるPOD(組み込み型を含む)であり、それらが道路に沿ってさらに変化する可能性がない場合(たとえば、boolパラメータの例など)、間違いを犯す心配なしに省略できます。

.h / .cppファイルの宣言の違いについては知りませんでしたが、それはある程度の意味があります。マシンコードレベルでは、何も「const」ではないため、(。hで)関数を非constとして宣言すると、コードはconst(最適化は別として)として宣言した場合と同じになります。ただし、関数(.ccp)の実装内で変数の値を変更しないようにコンパイラーに依頼するのに役立ちます。変更を許可するインターフェイスから継承している場合に便利ですが、必要な機能を実現するためにパラメーターに変更する必要はありません。


0

私はそのようなパラメーターにconstを設定しません-(boolean&とは対照的に)booleanが一定であることを誰もがすでに知っているので、これを追加すると人々は「待って、何ですか?」または、参照によってパラメーターを渡していることも。


4
(パフォーマンス上の理由で)参照渡しでオブジェクトを渡したいが、変更はしたくない場合があるため、constは必須です。そのようなすべてのパラメーター(ブール値であっても)を保持することは、コードを読みやすくするための実践的な方法です。
gbjbaanb 2008

0

constで覚えておくべきことは、最初からconstにする方が、後で試すよりもずっと簡単なことです。

何かを変更しない場合は、constを使用します。これは、関数の動作と期待される動作を説明する追加のヒントです。それらのいくつか、特にc-stringを受け入れるもので実行できるC APIをたくさん見ました!

ヘッダーよりもcppファイルのconstキーワードを省略する傾向がありますが、カットアンドペーストする傾向があるため、両方の場所に保持されます。コンパイラーがそれを許可する理由はわかりませんが、コンパイラーのものだと思います。ベストプラクティスは、両方のファイルにconstキーワードを含めることです。


これはまったくわかりません。 cppファイル(関数定義)でそれを省略したくなるのはなぜですか?それが実際に何かを意味し、エラーをキャッチできる場所です。 両方の場所にconstを配置することがベストプラクティスであると思うのはなぜですか?ヘッダーファイル(関数宣言)では、何も意味せず、APIが雑然としています。declとdefnをまったく同じに見えるようにすることには小さな価値があるかもしれませんが、APIを散らかす問題と比較すると、それは本当に小さなメリットだと私には思えます。
Don Hatch、

@DonHatch 8年後、すごい。とにかく、OPが言ったように、「関数宣言のパラメーターからconstを省略できるが、関数定義に含めることができることも知って驚きました」。
gbjbaanb

0

関数は変数のコピーをとにかく変更することしかできないので、値パラメーターを「const」にする理由は本当にありません。

「const」を使用する理由は、参照によってより大きなもの(たとえば、多数のメンバーを持つ構造体)を渡す場合です。その場合、関数はそれを変更できません。むしろ、従来の方法で変更しようとするとコンパイラーは文句を言うでしょう。誤って変更されるのを防ぎます。


0

Constパラメーターは、パラメーターが参照(参照またはポインター)によって渡される場合にのみ役立ちます。コンパイラーがconstパラメーターを検出すると、パラメーターで使用されている変数が関数の本体内で変更されていないことを確認します。なぜ誰かが値渡しパラメータを定数として作成するのですか?:-)


多くの理由で。値渡しパラメーターを作成すると、次のconstように明確に示されます。 'これを変更する必要がないため、宣言します。後で修正しようとする場合は、コンパイル時エラーを表示して、間違いを修正するか、const「」のマークを外してください。したがって、コードの衛生と安全の両方の問題です。実装ファイルに追加するために必要なことはすべて、人々が純粋な反射、IMOとして行うことです。
underscore_d

0

パラメーターは値で渡されるため、呼び出し側の関数の観点からconstを指定してもしなくても、違いはありません。値渡しパラメーターをconstとして宣言しても意味がありません。


0

例のすべてのconstには目的がありません。C ++はデフォルトで値渡しであるため、関数はこれらの整数およびブール値のコピーを取得します。関数がそれらを変更しても、呼び出し元のコピーは影響を受けません。

だから私は余分な定数を避けます

  • 彼らは冗長です
  • 彼らはテキストを散らかします
  • それらは、有用または効率的な場合に、渡された値を変更できないようにします。

-1

質問が「少し」古くなっていることは知っていますが、私がそれに遭遇したとき、他の誰かも将来そうするかもしれません... ...私はまだコメントを読むために貧しい仲間がここにリストアップすることを疑っています:)

私たちはまだCスタイルの考え方に制限されているようです。OOPパラダイマでは、型ではなくオブジェクトをいじります。Constオブジェクトは、特にconstオブジェクトとは概念的に異なる場合があります。具体的には、論理的なconstの意味で(ビットごとのconstとは対照的に)です。したがって、関数paramsのconstの正しさが(おそらく)PODの場合は過度に注意深くても、オブジェクトの場合はそうではありません。関数がconstオブジェクトで機能する場合は、そのようにする必要があります。次のコードスニペットを検討してください

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ps .:(const)参照がここではより適切であり、同じ動作を提供すると主張するかもしれません。そうですね。他の場所で見ることができるものとは異なる写真を与えるだけ...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.