書籍転載
|
|
Page1
Page2
|
●10.1.12 代入演算子
代入は初期化とは違います。前項ではコピーコンストラクタを自分で定義することによって、Aオブジェクトを別のAオブジェクトを使って初期化できるようになりました。そのコピーコンストラクタを、次のような代入で利用することはできません。
A a1; |
代入の際には、コピーコンストラクタではなく代入演算子(operator=)が利用されます。このコードのa2=a3は、a2.operator=(a1)と同じ意味です。コピーコンストラクタと同様に、代入演算子もコンパイラが自動生成します。単純に代入すればよいフィールドのみから成るようなオブジェクトの代入には、この代入演算子で十分です。
|
|
[サンプル]10-assign1.cpp |
コピーコンストラクタの結果を確認したときと同じ理由で、代入演算子も正しく動作していることがわかります。
コピーコンストラクタの場合と同じ理由で、メンバとしてポインタを持っていて、それが指すオブジェクトもコピーしたい場合には、自動生成される代入演算子は役に立ちません。そのため、代入演算子を自分で定義しなければなりません。代入演算子は次のように定義します(宣言と定義を分けることもできます)。
|
|
[構文]代入演算子の定義 |
代入は次のような手順で行います。
(1)idを複製する
(2)pBをtmpに保存する
(3)pBが指すオブジェクトを複製する(自動生成されたコピーコンストラクタを使う)
(4)tmpが指すオブジェクトを削除する
(5)自分自身への参照を返す
代入演算子では、最後に自分自身への参照を返すことが大切です。これを忘れて戻り値なし(void)にしてしまうと、「a3=a2=a1」のような式をうまく処理できなくなります。この式は、「a3=(a2=a1)」のように右から評価されますが、「a2=a1」の評価結果はa2であるべきです。そうなるためには、代入演算子が自分自身への参照を返さなければならないのです。
代入演算子を定義すると次のようになります。実行すると、「a3=a2=a1」のような場合でも正しく代入が行われていることがわかります。
|
|
[サンプル]10-assign2.cpp |
以上のように、デストラクタとコピーコンストラクタ、代入演算子を、コンパイラによって自動生成されるものの代わりに自分で実装しなければならないのは、メンバにポインタがあるときです。3つのメソッドのうちどれか1つに自分で実装する必要性を感じるときは、おそらく他の2つのメソッドも自分で実装しなければならないでしょう。
●10.1.13 標準コンテナの利用
自分で実装したクラスのオブジェクトを、コンテナ(第9章)の要素にすることができます。コンテナとしてstd::vectorやstd::listを用いる場合には、何も問題はありません。その一方で、std::setやstd::mapを用いる場合には、比較演算子「<」を定義しておかなければなりません。9.2節で述べたようにstd::setやstd::mapの要素は、常に並べ替えられた状態になっていなければなりませんが、並べ替えが可能であるためには、順番を比較できなければなりません。そして、順番の比較のためには、比較演算子「<」が必要なのです。
次のような簡単なクラスを考えましょう。
struct Person |
次のコードのように、Personをsetの要素にすることはできません。Personのオブジェクトを比較する方法がわからないからです。
set<Person> people; //Personを要素とするset |
比較演算子は、左辺が右辺より小さいときにtrueが返るように定義します。たとえば、Personオブジェクトどうしを年齢で比較する演算子「<」は次のように定義できます。
bool operator<(const Person& lhs, const Person& rhs) { return lhs.age<rhs.age; } |
このように演算子を定義し直すことを、演算子のオーバーロードと言います*7。演算子をオーバーロードしておけば、Personオブジェクトをsetの要素にすることができます。
*7 演算子&& と||をオーバーロードするとショートサーキット評価(3.2.10項)が行われなくなるので注意してください。 |
|
|
[サンプル]10-set.cpp |
実行結果は次のようになり、std::setから年齢の小さい順に取り出せていることが確認できます。
Taro (32) |
Personオブジェクトどうしを名前のアルファベット順で比較する演算子「<」は次のように定義できます(string型同士の比較は<string>に用意されています)。
bool operator<(const Person& lhs, const Person& rhs) { return lhs.name<rhs.name; } |
このように演算子を定義して、Personオブジェクトをsetの要素にして取り出すと、次のように名前のアルファベット順になっていることを確認できます。
Jiro (50) |
【コラム】関数オブジェクト | |||
メソッドとしてoperator()を持っているオブジェクトを関数オブジェクトと言います。簡単な例を以下に示します。
Greetingオブジェクトのメソッドoperator()は、フィールドnameの後に引数nameを追記して出力します。このように、関数オブジェクトは、オブジェクトであるにもかかわらず、関数のように使うことができます。関数オブジェクトは内部状態を持つ関数であり、さまざまな場面で応用できます。9.3.5項では、<algorithm>の関数for_each()に関数ポインタを渡す例を紹介しましたが、関数ポインタを使える場所では関数オブジェクトも使えます。 |
●10.1.14 テンプレートクラス
5.2.2項で紹介したテンプレート関数と同様に、クラスもテンプレートにすることができます。たとえば、第9章で紹介したvectorは、vector<int>やvector<double>のように、要素の型を指定できるテンプレートクラスです。テンプレートクラスは次のように定義します。
|
|
[構文]テンプレートクラスの定義 |
例として、数を2つ保持するだけの簡単なクラスを以下に示します。
|
|
[サンプル]10-template-class.cpp |
テンプレートクラスのオブジェクトを生成する際には、Point<int>やPoint<double>のように型を指定する必要があります。このコードで対応しているのは、int型とdouble型の2つだけですが、return文で利用している演算子「*」や「+」に対応した型ならば、どんなものでもこのテンプレートクラスPointを利用することができます。
■
次回は、ここまでのクラスの機能を踏まえたうえで、オブジェクト指向プログラミングの機能を解説します。
INDEX | ||
[書籍転載]文法からはじめるプログラミング言語Microsoft Visual C++入門 | ||
C++のクラスをマスターしよう(後編) | ||
1.コピーコンストラクタ/暗黙の変換 | ||
2.代入演算子/標準コンテナの利用/テンプレートクラス | ||
「文法からはじめるプログラミング言語Microsoft Visual C++入門」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|