あなたはconst
をどこまで使いますか?必要に応じて関数をconst
にするだけですか、それとも独り占めしてどこでも使用しますか。たとえば、単一のブールパラメータをとる単純なミューテータを想像してみてください。
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)
これには理由がありますか?それは私には少し変わっているようです。
その理由は、パラメータのconstは関数内でローカルにしか適用されないからです。それはデータのコピーを処理しているからです。これは、関数シグネチャがとにかく本当に同じであることを意味します。しかしこれを多くするのはおそらく悪いスタイルです。
私は個人的には参照とポインタのパラメータを除いてconstを使わない傾向があります。コピーされたオブジェクトの場合は、実際には関係ありませんが、関数内での意図を示すものであるため安全です。それは本当に判断の呼びかけです。私は何かをループするときにconst_iteratorを使う傾向があり、それを修正するつもりはないので、参照型のconstの正確さが厳密に維持されている限り、私はそれぞれの人に推測します。
"呼び出し側のオブジェクトは変更されないため、引数が値で渡される場合、constは無意味です。"
違う。
それはあなたのコードとあなたの仮定を自己文書化することです。
あなたのコードに多くの人が取り組んでいて、あなたの関数が自明でないなら、あなたは "const" anyとあなたができるすべてのものをマークするべきです。産業強度の高いコードを書くときは、同僚ができる限りの方法であなたを導こうとする精神病者であると常に考える必要があります(特に、将来は自分自身になることが多いため)。
その上、誰かが先に述べたように、それはコンパイラがものを少し最適化するのを助けるかもしれません(それはロングショットですが)。
時々(あまりにも多くの場合)私は他の誰かのC++コードを解かなければなりません。そして私たちは皆、他の誰かのC++コードはほとんど定義上完全な混乱であることを知っています:だからローカルデータフローを解読するために私が最初にすることを置きます - constコンパイラが鳴き始めるまでのすべての変数定義で。これは、呼び出し側によって初期化された単なる空想ローカル変数であるため、同様に定数修飾値引数を意味します。
ああ、私は変数がデフォルトでconstであり、mutableがconstでない変数に必要であったことを望みます:)
次の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はAPIの観点からは良くありません:
呼び出し側またはAPIユーザーに意味のある約束をすることなく、値によって渡される組み込み型パラメーターのために余分な定数を追加するAPIを混乱させる(実装を妨げるだけです)。
不要なときにAPIに含まれる 'const'が多すぎると、 "crying wolf"のようになります。結局、人々は 'const'を無視し始めます。
これらの最初の2つの点では、APIの追加のconstに対する "reductio ad absurdum"引数が適しています。さらに多くのconstパラメーターが適切であれば、constを持つことができるすべての引数にはconstがあります。実際、それほど本当に良ければ、constをパラメータのデフォルトにし、パラメータを変更したい場合にのみ "mutable"のようなキーワードを使用します。
それでは、できる限り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が真でない場合、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-happyの支持者は、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を散らすような目障りなもの、いらいらさせるもの、浅くて意味のない約束、不必要な邪魔をするものであり、ときには非常に危険なミスにつながる。
c ++では、constがデフォルトになっているはずです。このような :
int i = 5 ; // i is a constant
var int i = 5 ; // i is a real variable
私がC++を生計用にコーディングしたとき、私は可能な限りすべてを調べました。 constを使用することは、コンパイラがあなたを助けるための素晴らしい方法です。たとえば、メソッドの戻り値を制限することで、次のような入力ミスを防ぐことができます。
foo() = 42
あなたが言ったとき:
foo() == 42
Foo()が非const参照を返すように定義されている場合:
int& foo() { /* ... */ }
コンパイラは、関数呼び出しによって返された匿名の一時変数に値を代入することを喜んで許可します。それをconstにする:
const int& foo() { /* ... */ }
この可能性を排除します。
このトピックに関するcomp.lang.c ++。モデレート の古い「今週の教祖」の記事 には、このトピックに関する良い議論があります。
対応するGOTWの記事は、Herb SutterのWebサイト にあります 。
私はあなたの値パラメータをconstと言います。
このバグの多い関数を考えてください。
bool isZero(int number)
{
if (number = 0) // whoops, should be number == 0
return true;
else
return false;
}
Numberパラメータがconstの場合、コンパイラは停止してバグについて警告します。
私はデータだけであり、関数によって変更されない参照(またはポインタ)である関数パラメータにconstを使います。つまり、参照を使用する目的がデータのコピーを回避することであり、渡されたパラメータの変更を許可しないことです。
あなたの例では、ブール値のbパラメータにconstを設定することは、実装に制約を設定するだけで、クラスのインターフェースには寄与しません(ただし、パラメータを変更しないことをお勧めします)。
の関数シグネチャ
void foo(int a);
そして
void foo(const int a);
これはあなたの.cと.hを説明するものと同じです。
アサフ
->*
演算子または.*
演算子を使用する場合は必須です。それはあなたがのような何かを書くことを妨げます
void foo(Bar *p) { if (++p->*member > 0) { ... } }
私はほとんど今やった、そしておそらくあなたが意図していることをしない。
私が言うつもりだったのは
void foo(Bar *p) { if (++(p->*member) > 0) { ... } }
Bar *
とconst
の間にp
を入れると、コンパイラーはそれを教えてくれたでしょう。
ああ、大変だ。一方では、宣言は規約であり、const引数を値渡しすることは意味がありません。一方、関数の実装を見ると、引数の定数を宣言した場合にコンパイラに最適化の機会が多くなります。
値パラメータ 'const'をマークすることは間違いなく主観的なことです。
しかし、私は実際にはあなたの例のように、値パラメータをconstとマークすることを好みます。
void func(const int n, const long l) { /* ... */ }
私にとっての価値は、関数パラメータ値が関数によって決して変更されないことを明確に示しています。最初と最後で同じ値を持ちます。私にとっては、これは非常に機能的なプログラミングスタイルのスタイルを維持することの一部です。
短い関数では、引数が関数によって変更されないことは通常明らかなので、そこに 'const'を持つことはおそらく時間とスペースの浪費です。
しかし、より大きな関数については、それは実装ドキュメントの形式であり、それはコンパイラによって強制されます。
'n'と 'l'を使って計算を行っても、どちらか一方または両方が変更されている場所を逃したため、別の結果が出ることを恐れずにその計算をリファクタリング/移動できます。
これは実装の詳細なので、実装で使用されているのと同じ名前で関数のパラメータを宣言する必要がないのと同様に、ヘッダーで値パラメータconstを宣言する必要はありません。
あなたが呼び出し側のオブジェクトを変更することはないので、引数が値によって渡されるときconstは無意味です。
関数の目的が渡された値を変更することである場合を除き、参照渡しで渡す場合はconstをお勧めします。
最後に、現在のオブジェクトを変更しない関数(this)は、constとして宣言することができます。例を以下に示します。
int SomeClass::GetValue() const {return m_internalValue;}
これは、この呼び出しが適用されるオブジェクトを変更しないという約束です。言い換えれば、あなたは呼び出すことができます:
const SomeClass* pSomeClass;
pSomeClass->GetValue();
関数がconstではなかった場合、これはコンパイラの警告になります。
これは有効な議論ではないかもしれません。しかし、関数コンパイラ内でconst変数の値を増やすとエラーになります: "error:読み取り専用パラメータの増分"。つまり、関数内で誤って変数を変更しないようにする方法として、constキーWordを使用できます(これは/ read-onlyには想定されていません)。そのため、コンパイル時に誤って実行した場合は、そのことがコンパイラに通知されます。あなたがこのプロジェクトに取り組んでいるのがあなただけではない場合、これは特に重要です。
あなたが言及したケースでは、それはあなたのAPIの呼び出し元には影響しません、それはそれが一般的に行われない理由です(そしてヘッダーに必要ではありません)。それはあなたの関数の実装に影響するだけです。
特に悪いことではありませんが、APIに影響を与えず、タイピングが追加されるため、利点はそれほど大きくありません。したがって、通常は行われません。
私はできる限りconstを使います。パラメータの定数は、値を変更しないことを意味します。これは、参照渡し時に特に役立ちます。 const for functionは、関数がクラスメンバを変更しないことを宣言します。
値渡しのパラメータにはconstを使用しません。呼び出し元は、あなたがパラメータを変更するかどうかを気にしません。それは実装の詳細です。
本当に重要なことは、インスタンスが変更されていない場合、メソッドをconstとしてマークすることです。そうしないと、多くのconst_cast <>が発生したり、constにマークを付ける必要がある他のメソッドを呼び出すために、constにマークを付けたりすると多くのコードを変更する必要が生じることがあります。
また、ローカル変数を変更する必要がない場合は、ローカル変数をconstとマークする傾向があります。 「動く部分」を識別しやすくすることで、コードを理解しやすくすると私は考えています。
コンパイラの最適化について: http://www.gotw.ca/gotw/081.htm
可能な限り私はconstを使う傾向があります。これは、コンパイラが他の方法では実行できないような追加の最適化を可能にするためです。私はこれらの最適化が何であるかわからないので、それがばかげているように見える場合でも、私はいつもそれをします。
私が知っているすべての人にとって、コンパイラは非常によくconst valueパラメータを見て、「やあ、この関数はとにかくそれを変更していないので、参照渡ししてクロックサイクルを節約することができます」と言う。関数のシグネチャを変更するので、それがそのようなことをするとは思わないが、それは重要なことです。たぶんそれはいくつかの異なるスタック操作か何かをします...重要なのは、私にはわかりませんが、コンパイラーが私を恥じることにつながるより賢くしようとすることを私は知っています。
C++には、正当性を保つための追加の手荷物があります。そのため、さらに重要になります。
要約する:
std::vector::at(size_type pos)
。標準ライブラリに十分なものは私にとっても良いものです。パラメータが値渡しされている(参照ではない)場合、通常、パラメータがconstとして宣言されているかどうかに大きな違いはありません(参照メンバが含まれていない限り、組み込み型では問題ありません)。パラメータが参照またはポインタの場合は、ポインタ自体ではなく、参照/ポイント先メモリを保護するのが一般的です(参照自体をconstにすることはできないと思います。レフェリーを変更できないので問題ない)。 。できる限りのことをすべてconstで保護するのは良い考えのようです。パラメータがPOD(ビルトインタイプを含む)だけで、それらが道路に沿ってさらに変化する可能性がない場合(たとえば、あなたの例ではboolパラメータ)、ミスをすることを恐れずにそれを省略することができます。
.h/.cppファイルの宣言の違いについては知りませんでしたが、意味があります。マシンコードレベルでは、 "const"は何もないので、(.h内の)関数を非constとして宣言した場合、コードはconstとして宣言した場合と同じになります(最適化は別として)。ただし、関数のインプリメンテーション(.ccp)内で変数の値を変更しないようにコンパイラーに参加させるのに役立ちます。変更を許可するインターフェースから継承している場合には便利ですが、必要な機能を実現するためにパラメーターを変更する必要はありません。
私はそのようなパラメータにconstを置くことはしないでしょう - 誰もがすでに(boolean&ではなく)booleanが定数であることを知っています、それでそれを加えることは人々が「待つ、何?」と思わせるでしょう。あるいは、パラメータを参照渡ししているという場合もあります。
constで覚えておくべきことは、最初からconstにする方が後で試してみるよりもずっと簡単だということです。
変更したくない場合はconstを使用してください。これは、関数が何をするのか、何を期待するのかを説明する追加のヒントです。私は、それらのうちのいくつか、特にC文字列を受け付けるものでできるC APIをたくさん見ました!
ヘッダーよりcppファイルのconstキーワードを省略したいと思いますが、それらをカットアンドペーストする傾向があるので、両方の場所に残しておきます。なぜコンパイラがそれを許可しているのか私にはわかりません、私はそのコンパイラのことを推測します。ベストプラクティスは、必ず両方のファイルにconstキーワードを入れることです。
あなたの例の中のconstはすべて目的を持っていません。 C++はデフォルトで値渡しであるため、関数はこれらの整数とブール値のコピーを取得します。関数がそれらを変更しても、呼び出し元のコピーは影響を受けません。
だから私は余分な定数を避けたいと思います
関数はとにかく変数のコピーを変更することしかできないので、値パラメータを "const"にする理由は本当にありません。
"const"を使用する理由は、もっと大きなもの(例えば、たくさんのメンバを持つ構造体)を参照渡ししている場合です。そうではなく、従来の方法で修正しようとすると、コンパイラは文句を言います。誤って変更されるのを防ぎます。
パラメータは値渡しであるため、呼び出し側関数の観点からは、constを指定してもしなくても違いはありません。値渡しをconstとして宣言しても意味がありません。
定数パラメータは、パラメータが参照によって、すなわち参照またはポインタによって渡されるときにのみ有用である。コンパイラがconstパラメータを検出した場合、そのパラメータで使用されている変数が関数の本体内で変更されていないことを確認します。なぜ誰かが値によってパラメータを定数にしたいのですか? :-)