参照の戻り値に対して代入する

メンバ関数に代入する - (void*)Pないとの時にも調べたのですが、メンバ関数に限らず戻り値が参照の場合にその関数を左辺値として扱うことができるようです。

#include <iostream>
using namespace std;

int& foo (int& num) {
    return num;
}

int main () {
    int num = 10;
    
    // 戻り値が参照の関数に対して代入
    foo(num) = 200;
    
    cout << num << endl;
    return 0;
}
$ main
200

戻り値が参照型なのでそこに対して代入処理を行うのは構文上正しいということになります。

挿入イテレータ

http://www.geocities.jp/ky_webid/cpp/library/013.html

値の追加を行うことができるイテレータのことを挿入イテレータを言います。

とりあえず実装してみます。

#include <iostream>
#include <list>
using namespace std;

int main () {
    typedef list<int> listint;
    
    listint data;
    
    // 先頭にデータを挿入するためのイテレータ
    front_insert_iterator<listint> iit( data );
    
    *iit = 10;
    *iit = 20;
    *iit = 30;
    
    listint::iterator it  = data.begin();
    listint::iterator end = data.end();
    for( ; it != end; ++it ) {
        cout << *it << endl;
    }
    
    return 0;
}
$ main
30
20
10

宣言したイテレータ変数に代入していくだけで先頭に値が格納されていきます。

ですがこれって実質push_front関数が呼ばれているだけなので手続き長くなる割りにやってることはしょぼいので存在意義がいまいちわからないですね・・・。イテレータのように扱えるというのがメリットになるようなケースがあるのかもしれません。

ちなみにイテレータではないですが、関数呼び出しで代入する方法も用意されているようなのでそれも実装してみます。

#include <iostream>
#include <list>
using namespace std;

int main () {
    typedef list<int> listint;
    
    listint data;
    
    front_inserter(data) = 10;
    front_inserter(data) = 20;
    front_inserter(data) = 30;
    
    listint::iterator it  = data.begin();
    listint::iterator end = data.end();
    for( ; it != end; ++it ) {
        cout << *it << endl;
    }
    
    return 0;
}
$ main
30
20
10

できました。あまり使い道はなさそうですが・・・。

さて先頭に対して挿入ができるなら後尾に対して挿入することもできます。

先ほどの

front_insert_iterator<listint> iit( data );

back_insert_iterator<listint> iit( data );

に変更するだけです。

関数形式の方も、back_inserterに変えるだけでOKです。

最後は汎用挿入イテレータです。これは指定の場所に挿入するイテレータです。

とりあえず使い方を見てみます。

#include <iostream>
#include <list>
using namespace std;

int main () {
    typedef list<int> listint;
    
    listint data;
    
    // 任意の場所に挿入するイテレータ
    // 挿入箇所のイテレータを第二引数で渡す
    insert_iterator<listint> iit( data, data.begin() );
    
    *iit = 10;
    *iit = 20;
    *iit = 30;
    
    listint::iterator it  = data.begin();
    listint::iterator end = data.end();
    for( ; it != end; ++it ) {
        cout << *it << endl;
    }
    
    return 0;
}

とりあえず例ではbeginのポインタを渡して見ました。

しっかしこれ、どう考えても普通にinsert関数でデータの挿入を行えば良いような気がするんですが、こういった用途があるんでしょうかね。一つ考えられるとしたらどんなコンテナであろうとも同じようなインターフェイスで挿入が行えるということなんでしょうか?うーん、insert関数使う場合も同じようなインターフェイスなのでそうでもないっぽいですね。このあたりは追々調べてみたいと思います。

ストリームイテレータ

http://www.geocities.jp/ky_webid/cpp/library/014.html

出力や入力に対してイテレータを使って値のやり取りができます。そのようなイテレータのことをストリームイテレータを言うようです。

まずは出力ストリームの動作を確認してみます。

#include <iostream>
#include <iterator>
using namespace std;

int main () {
    
    ostream_iterator<int> oit( cout, "\n" );
    
    // 出力ストリームにデータを送る
    *oit = 10;
    *oit = 20;
    *oit = 30;
    
    return 0;
}
$ main
10
20
30

表示されました。

コンストラクタの第一引数には使用するストリームの種類。今回はcoutをそのまま渡しています。

で第二引数はデリミタと呼ばれるもので、要は区切文字ですね。イテレータへ代入した値+デリミタが出力されるといった感じです。

#include <iostream>
#include <iterator>
using namespace std;

int main () {
    
    // デリミタを--にしてみる
    ostream_iterator<int> oit( cout, "--" );
    
    *oit = 10;
    *oit = 20;
    *oit = 30;
    
    return 0;
}
10--20--30--

と表示されます。


次に入力ストリームの動作を確認してみます。

#include <iostream>
#include <iterator>
using namespace std;

int main () {
    
    istream_iterator<int> iit( cin );
    istream_iterator<int> iit_eof;
    
    while ( iit != iit_eof ) {
        cout << "cin = " << *iit << endl;
            ++iit;
    }
    
    return 0;
}
$ main
100 200
cin = 100
cin = 200
z

こんな感じになります。

まずコンストラクタに使用する入力ストリームを渡します。今回はcinを渡しています。次に引数無しでインスタンス化した変数を用意します。これは入力の最後、つまりEOFを表します。

あとはwhileで入力の最後が来るまでループで入力を受付けするという処理になっています。

そして注目すべき点は、「z」を入力したらEOF扱いになり、ループを抜けて終了している点です。

これはistream_iteratorのテンプレート引数にint型を指定しているので、それ以外のデータが入力されたら終了するという処理になっているからです。便利ですね。

文字列も入力させたければstring型あたりを使うと良いのでしょうか。

#include <iostream>
#include <iterator>
#include <string>
using namespace std;

int main () {
    
    istream_iterator<string> iit( cin );
    istream_iterator<string> iit_eof;
    
    while ( iit != iit_eof ) {
        cout << "cin = " << *iit << endl;
            ++iit;
    }
    
    return 0;
}
$ main
abc 100
cin = abc
cin = 100
^Z

うまく動いていますね。(Crtl+Zの入力は前にC言語で習ったデータを終端を表す入力です。参考→http://www.geocities.jp/ky_webid/c/027.html

int i;とint(i);の違いについて

色々勉強したり試したりしてて分からないことがあったので記録。

以下のようなコードなのですが

int main () {
    int(i);
    int j;
    
    return 0;
}

CでもC++でも問題なく動きます。

つまりこれは変数宣言する際に、括弧で囲っても囲まなくてもまったく同じ意味になると考えて良いんでしょうか?

また、デフォルト値の設定も可能です。

int main () {
    int(i) = 10;
    int j  = 20;
    
    return 0;
}

これもCでもC++でもちゃんと動きます。

さらにC++の場合は、コンストラクタに渡すという意味で下記のようにも書けます。intだと分かりづらいのでクラスを定義して実験してみます。

class CSample {
public:
    CSample ()    { cout << "CSample()" << endl; }
    CSample (int) { cout << "CSample(int)" << endl; }
};

int main () {
    CSample(i)(10);
    CSample j(20);
    
    return 0;
}
$ main
CSample(int)
CSample(int)

動いていますね。

同じであると思っても良いんでしょうか。あまり明確に書かれた仕様が見つからないのでちょっと不安です。

int();とint(i);とint(1);の違いについての謎


の続きです。

括弧ありとなしで同じかと思ったんですが、どうも渡す引数によって挙動が変わるようです。

とりあえず以下の例を。

int main () {
    int();
    int(i);
    int(100);
    
    return 0;
}

これらは全て問題なくコンパイルが通ります。一体どういう風に解釈されているのかちょっとわかりません。

iの場合は変数宣言になり、引数無しや数値の場合は一体どういう風に解釈されているのかわかりません。

ということで一旦クラスにしてコンストラクタがどのように呼ばれているのか検証してみました。

#include <iostream>
using namespace std;

class CSample {
public:
    CSample () { cout << "CSample()" << endl; }
    CSample (int) { cout << "CSample(int)" << endl; }
};

int main () {
    CSample();
    CSample(i);
    CSample(100);
    
    return 0;
}
$ main
CSample()
CSample()
CSample(int)

なんだか面白い結果になりました。

引数無しや数値を渡した場合でもちゃんとコンストラクタが呼ばれています。しかし変数を指定してないのでインスタンス化がされたとしても実際のオブジェクトがありません。

ということはもしかして戻り値にオブジェクトが帰るのかな?と思って試してみました。

#include <iostream>
using namespace std;

class CSample {
public:
    CSample () { cout << "CSample()" << endl; }
    CSample (int) { cout << "CSample(int)" << endl; }
    void func () { cout << "func()" << endl; }
};

int main () {
    
    CSample obj1 = CSample();
    obj1.func();
    
    CSample obj2 = CSample(100);
    obj1.func();
    
    return 0;
}
$ main
CSample()
func()
CSample(int)
func()

おおうまく動いた。つまりこれはクラス括弧付きで呼ぶ場合、引数に変数名以外のものを渡すとそのクラスをインスタンス化したオブジェクトを戻り値として返すということなんですかね。

ということはオブジェクトの値渡しになるため、コピーコンストラクタが呼ばれているか試してみました。

#include <iostream>
using namespace std;

class CSample {
public:
    CSample () { cout << "CSample()" << endl; }
    CSample (const CSample&) { cout << "CSample(const CSample&)" << endl; }
};

int main () {
    CSample obj = CSample();
    
    return 0;
}
$ main
CSample()

あれれ??

呼ばれて無い・・・。CSample()の戻り値として返されたオブジェクトをobjに代入しているわけだから値渡しなわけでコピーコンストラクタが呼ばれないはずがありません。

一応普通に定義したオブジェクトを渡してみたところ、

#include <iostream>
using namespace std;

class CSample {
public:
    CSample () { cout << "CSample()" << endl; }
    CSample (const CSample&) { cout << "CSample(const CSample&)" << endl; }
};

int main () {
    CSample obj1;
    CSample obj2 = obj1;
    
    return 0;
}
$ main
CSample()
CSample(const CSample&)

ちゃんとコピーコンストラクタが呼ばれています。

かなり頭がMAXに混乱してきました。

CSample obj = CSample();

と書いた場合に内部で最適化されてコピーが発生しないように動いてくれてるということなのでしょうか?それとも僕が何か壮絶な勘違いをしているだけなのでしょうか?

ちなみにコピーコンストラクタが呼ばれないのであればprivateにしてても問題ないのかなと思って

#include <iostream>
using namespace std;

class CSample {
public:
    CSample () { cout << "CSample()" << endl; }
private:
    CSample (const CSample&) { cout << "CSample(const CSample&)" << endl; }
};

int main () {
    CSample obj = CSample();
    
    return 0;
}

としたところ、見事にコンパイルエラーが発生しました。

$ cl /W4 /EHsc main.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
main.cpp(12) : error C2248: 'CSample::CSample' : private メンバ (クラス 'CSample' で宣言されている) にアクセスできません
        main.cpp(8) : 'CSample::CSample' の宣言を確認してください。
        main.cpp(4) : 'CSample' の宣言を確認してください。

えーなんで。呼ばれて無いのにアクセスできませんって。呼ぶ予定は無いけどアクセスできるかどうかの判断は有効ってこと?謎が深まるばかりです。おそらく僕の何かひとつの小さな見落としからこんな疑問が出てるのだとは思いますが・・・。

とりあえず今読み進めているロベール本にこれらの疑問の答えが書かれていると良いのですが。

追記

コメント欄にて色々と教えて頂きました。ちゃんと時間をかけて理解していきたいと思います。

文字列リテラルの型

ロベールのC++入門講座 05-13

文字列リテラルの型は正確にはconst char型の配列だそうです。

てっきりポインタ型だと思っていました。

自分の目で確認するため、sizeofしてみました。

#include <iostream>
using namespace std;

int main () {
    cout << sizeof("foo bar baz") << endl;
    
    return 0;
}
$ main
12

確かにサイズが取れています。もしchar*型だったならせいぜい4バイト程度のはずです。

ですがここで一つ問題があります。

もしconst char型の配列なのだとしたら、char*型でアドレスを取れるはずがありません。以下のコードを見てください。

#include <iostream>
using namespace std;

int main (void) {
    const char str[] = "foo bar baz";
    char* p = str;
    
    return 0;
}
$ cl /W4 /EHsc main.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
main.cpp(6) : error C2440: '初期化中' : 'const char [12]' から 'char *' に変換できません。
        変換で修飾子が失われます。

このようにコンパイルエラーになるはずですが、文字列リテラルの場合はコンパイルエラーになりません。

int main (void) {
    // const無しのchar*に代入してるのに何故かコンパイルが通る
    char* p = "foo bar baz";
    
    return 0;
}

どうやらこれは、C言語との互換性のために無理やり通るようになっているようです。

そもそもC言語ではconst char型の配列であっても、const無しのchar*型へ代入できてしまいます。警告こそは出るものの、コンパイルエラーにはならないんですね。

ということでもしC++で文字列リテラルのconst付きを一気に強化しちゃったら動かなくなるCのコードが大漁に出る羽目になるのでこういった処置がとられてるんでしょうね。

ただ将来的なC++ではこの救済処置は廃止される予定らしいので今のうちからconst付きのchar*型を意識付けておく方が良さそうです。