Kikuchy's Second Memory

技術のこととか、技術以外のこととか、思ったことを書き留めています。

C++の値オブジェクトと参照オブジェクト

注意: この記事には不確かなことが書かれていることがあります。
C++ に詳しい諸兄におかれまして、もし間違いなどを見つけられましたら、コメント欄にてご意見をお寄せください。


最近、研究室にあったCで書かれたプログラムを、 C++ で書き直しています。
目的は、研究のため、今後の後輩のため、そしてマルチプラットフォーム化のためです。
特に3つ目の目的のため、初めて Qt を使ったプログラムを書いています。


さて、書くために C++ について勉強していて、こんな表記にぶつかりました。

// 値オブジェクト
ClassA a1;

// 参照オブジェクト
ClassA *a2 = new ClassA();

これは一体なんなのでしょうか。
どんなときにどちらを使えば良いのでしょうか。
そして、どう使えば便利なのでしょうか。
C# に慣れ親しんでいた私には違いがよくわからないので、調べてみました。

値オブジェクト

int 型のように、プリミティブな型と同じ表記でインスタンス化したオブジェクトです。
言葉の定義は、C++ クラス設計に関するノートによれば、

次のような特徴をもったオブジェクトを「値オブジェクト」といいます。

  • インスタンス自体よりも、保持するデータ値が重要な意味をもつ。
  • インスタンスが異なっても、同じ値を保持していれば同じオブジェクトと見なす。
  • 必要に応じて自由にコピーを作ることができる。
  • int や double などの基本データ型と同じセマンティックスで使用される。

C 言語で言う所の struct とほとんど同じような使い方をするクラスが、これに該当するようです。
例えば、「本」を表すこんなクラスがあったとします。

class Book {
public:
  string title;
  string author;
  bool isFavorite();

private:
  bool m_isFavorite;
};

これはインスタンスの存在そのものより、そこに格納されている値が重要ですよね。
また、値オブジェクトは immutable にするのが推奨されるとか。
そうですよね。値の状態が知らん間にコロコロ変わってたりされたら迷惑至極です。

参照オブジェクト

定義は、

次のような特徴をもったオブジェクトを「参照オブジェクト」といいます。

  • インスタンス自身が重要な意味をもつ。
  • 同じデータを持っていても、インスタンスが異なれば違うオブジェクトと見なす。
  • 通常コピーは作らない。

ということで、何かしらの機能を提供するようなクラスのインスタンスがこれにあたりそうです。
例えば、 TCP のソケット通信を行う、 Qt の QTcpSocket などがこれにあたるでしょうか。
同じサーバーに接続するクライアントが二つあったとしても、この二つは別物でなければいけません。同じものだと通信が混乱します。
それに、コネクションのコピーを作成することも、通常はあり得ませんよね。

また、このオブジェクトは mutable で良いそうです。

どう使うか

私が気にしているのは、以下の点です。

  • オブジェクトの寿命はどうなっているのか
  • メソッドの引数に渡す時はどうするのか
  • メソッドの戻り値にする時はどうするのか

一つずつ見て行きましょう。

オブジェクトの寿命はどうなっているのか

値オブジェクトについては、 @satoru_takeuchi 先生に教えていただきました。

ということで、インスタンスを生成したスコープの中でのみ生きているようです。
スコープの外に持ち出すには、値のコピーが必要ですね。


参照オブジェクトについてはよく言われている通り、 delete 演算子で消すまでオブジェクトが生き残り続けます。
よくメモリリークの原因になるあれですね。

メソッドの引数に渡す時はどうするのか

例を見れば早そうですね。

// 値オブジェクトの渡し方 その1(参照渡し)
void methodA(Class &a);

// 値オブジェクトの渡し方 その2(値コピー)
void methodB(Class a);

// 参照オブジェクトの渡し方
void methodC(ClassA *a);

値オブジェクトの渡し方は二つ。
「その1」の方は、このメソッド (methodA) を呼び出しているスコープが生きているときならば引数のオブジェクトを参照できますが、スコープが切れると参照できなくなりそうな気がします。非同期処理をするときには注意が必要そうです。
「その2」の方は、値が丸々コピーされているので、外のスコープが切れていても参照できます。また、メソッド終了時に自動的に破棄されます。便利ですが、コピーにコストがかかるのが難点。

参照オブジェクトの渡し方については、特に何も言う事は無いですね。

メソッドの戻り値にする時はどうするのか
// 値オブジェクトを返す
ClassA methodD();

// 参照オブジェクトを返す
ClassA* methodE();

値オブジェクトの場合は、オブジェクトのコピーが行われます。時間がかかりそうですね。
参照オブジェクトの場合は、参照だけが返ります。受け取った先でメモリの開放を行わなければいけません。


ある程度、 C++ のことが分かった気がします。
しかし、 C++ でゲームを作っている友人に聞いたところ、
「最近はスマートポインタとかもあるよ」
とのこと。
まだまだ底が見えないです。


参考: