値渡しにconstを付ける?付けない?
最近会社でプロジェクトメンバーにソースコードレビューしてもらう習慣ができました。
自分のソースコードをリーダー・後輩にレビューしてもらったんですが自分のコードを晒す事と人のコードを読む事に勝る勉強はない。
それで今回は一つの指摘点にスポットを当ててみた。
//これって引数の値に変更がないのならconst付けたほうがいいんじゃないでしょうか? void func(int x) { // 実装 }
そもそもconstを付ける意味って?
オブジェクトの場合
無駄なコピーをしないために参照渡しをする。
そのときに渡した値に変更を加える関数かどうかでconstを付けるか付けないかが決まる。
void func1(std::string str) { // sはstrに値をコピーする。(コピーコンストラクタが呼ばれる) // strは値を変更する事が出来る // strの値を変更してもsにはなんら影響はない } void func2(std::string& str) { // strはsの参照を持つためコピーされない // strは値を変更する事が出来る // strの値を変更するとsに影響する // 関数内でstrにデータを設定するようなデータ設定関数に利用される } void func3(const std::string str) { // sはstrに値をコピーする。(コピーコンストラクタが呼ばれる) // strは値を変更する事が出来ない // strの値を変更してもsにはなんら影響はない } void func4(const std::string& str) { // strはsの参照を持つためコピーされない // strは値を変更する事が出来ない // strの値を変更してもsにはなんら影響はない // 関数内でstrを参照するようなデータ参照関数に利用される } int main() { std::string s("hoge"); func1(s); func2(s); func3(s); func4(s); return 0; }
func1,func3は余分にコピーコンストラクタが呼ばれるため普通は使わない。
組み込み系の場合
intなどの組み込み系は
Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)に
書かれているように値渡しのほうが効率がいい。
void func(int x) { // 実装 }
値渡しにconstを付ける意味は? 答え (関数宣言では)全く意味がない。
値をコピーするものなので渡した値のコピーに変更が加えられようがられまいが渡した値には影響がない。
つまり値渡しにconstを付けるのは冗長であるという事になる。
しかしc++には奇妙な仕様があるのでした。
C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス (C++ in‐depth series)に書かれているところによると、
以下のように関数宣言は非constで、関数定義はconstにする事ができる。
void func(int x); void func(const int x) { // 実装 }
このように記述するメリットは以下のようになる。
- 関数宣言では冗長な表現をしないで
- 実装でxを変更するようなコードを書くとコンパイルエラーになる事が保証される
まとめ
これはあくまで僕の意見ですが
組み込み系を仮引数にもつ関数の場合3パターンの記述方法があるのかなと思います。
// パターン1 // 宣言ではconstを付けずに定義では付ける void func(int x); void func(const int x) { // xを変更しない関数に使える // 関数宣言では冗長な表現をしない // 関数定義ではxを変更するとコンパイルエラーになる事が保証できる // デメリットとして宣言と定義が合っていないのでこの仕様について知らない人がメンテすると混乱させる事になる } // パターン2 // 宣言も定義もconstを付けない void func(int x); void func(int x) { // xを変更する/しない関数に使える // 関数宣言では冗長な表現をしない // 関数定義ではxを変更できてしまうので自己管理が必要 } // パターン3 // 宣言も定義もconstを付ける void func(const int x); void func(const int x) { // xを変更しない関数に使える // 関数宣言では冗長な表現をする // 関数定義ではxを変更するとコンパイルエラーになる事が保証できる }
ちなみに僕は今までxを変更しない関数もパターン2で実装してきましたが、
パターン1を導入してみようかと思っています。