Subscribed unsubscribe Subscribe Subscribe

引数を参照とポインタのどちらで渡すか

C++

暫く触っていないとすぐ忘れるのでメモ.クラスを関数の引数に取るとき,参照で渡すかポインタで渡すか?結論をさっさと書くと,constなら参照,非constならポインタをデフォルトとする.constポインタはconst参照の使用が合理的でない場合に使える.非const参照は使ってはいけない.

constの有無 ポインタ 参照
const △ 使える ○ 推奨
const ○ 推奨 × 使ってはいけない!

結局好みの問題でもある気がするけど,GoogleのC++スタイルガイド,プログラミング言語C++第3版大規模C++ソフトウェアデザインあたりでも上と同じような主張がされているということで正当化してみます.

なぜ非constの参照がまずいのか

引数が変更されるかどうかが構文だけから分かりにくい.たとえば以下のコードでcが意図せず変更されてしまったとき,どの関数が悪さをしているのか調べるために,ヘッダファイルを探し回る羽目になる.

int main(void){
   Class c;
   foo(c);// void foo(const& Class); in foo.h
   bar(c);// void bar(&Class); in bar.h
   baz(c);// void baz(const& Class); in baz.h
}

「非constならポインタである」という規則を一貫して守っておけば,ユーザーは引数に&をタイプするとき,それが関数側で変更される可能性があることを認識できる.

int main(void){
   Class c;
   foo(c);// const
   bar(&c);// 非constの可能性がある!
   baz(c);// const
}

const参照ではなくconstポインタを使うケース

(1)STLアダプタみたいな,const参照を使うことができない場合.
(2)オブジェクトの生存期間中にその引数が有効である必要があることを強調したい場合.
以下のケースではadd関数はbのコピーを取らずにアドレスを持つため,bはfの生存中に有効である必要があるが,呼出し側からはそれが分かりづらい.ポインタであれば,その危険性に気づきやすい…かもしれない.Googleのガイドでは,こういう場合にはコメントに明記するのがベストだと書かれている.

class Foo {
private:
   const Bar* bar_;
public:
   void add(const Bar& bar){
      this->bar_ = &bar;
   }
};
int main(void){
   Foo f;
   Bar b;
   f.add(b);//const参照渡し.Fooの内部にbのアドレスを保持
}

(3)const参照による暗黙の一時オブジェクト生成を嫌う場合.

class Class {
private:
  const Class* myclass;
public:
  Class(int i);
  void set(const Class& c) { this->myclass = &c; }
  const Class* get() const { return this->myclass; }
};

int main(void){
    Class a(10);
    a.set(1);//一時オブジェクトが生成
            //オブジェクトはsetの関数内でのみ有効であることが保障される
    Class* b = a.get();
    //bで何かする→!
}

a.setにint型を渡したことで,setのスコープ内でのみ有効なClassの一時オブジェクトが生成される.そのオブジェクトへのポインタをClass内部で持とうとすると,よろしくない事態になる.
この場合,(1)引数を非const参照にする,(2)引数をconstポインタにする,(3)Classのコンストラクタをexplicitとして宣言する,のいずれかの変更を行うと,一時オブジェクトを生成しないためコンパイルエラーを出すようになる.ただし(1)は冒頭から否定しているので,取りうるのは(2)か(3)のみ.ライブラリに手を加えられない場合は,引数をconstポインタにすることになる.