第6回 C++・テンプレート、例外処理

C++明示的タイプ変換あれこれ

C言語では、intとlong, doubleとfloat、あるいはdouble型の変数をinte型に代入するような場合、暗黙的なデータ型の変換が発生しました。一方で、キャスト演算をすることで、明示的な型の変換が行われる場合もありました。 C++では、幾つかの予約語で、明示的な型変換を行えるようになり、できるだけ暗黙的な型の変換を避けることが推奨されています。つまり、プログラム作者の変換意図がわかるような変換演算をすることが期待されています。代表的な明示的型変換をまとめておきます。

型変換の方法 意味
static_cast
暗黙の型変換でも定義されている型変換
	
char x = 'a';
int* p1 = &x;
int *p2 = static_cast<int *>(&x);

class B {...};
class D: B { ...};
B* pb = new D;
D* pd = static_cast <D*>(pb);
	
reinterpret_cast
互いに関係しないデータ型の間での強引な変換
(同一アドレスの中身の解釈の変更など)
IO_device *d1 = reinterpret_cast <IO_device *>(0Xff00);
	
dynamic_cast
クラスの階層(継承)におけるポインタ変換
polymorphicな場合(基底クラスに仮想関数があること)に可能。
Circle *c = dynamic_cast <Shape *>(shape);
	


テンプレート(スタック例)

前回、簡単なテンプレートを紹介しました。 ここでは、スタックを使ったテンプレート例を紹介します。
Stack.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* プログラム6-1 テンプレートを用いたスタック例 */
#ifndef _STACK
#define _STACK
#include <iostream>
using namespace std;

template <typename T>
class Stack {
    int size; /* スタックサイズ */
    int top;/* スタックのトップ */
    T *stackPtr;/* T型クラス(データ型)へのポインタ */
    public:
        Stack(int s): size(s > 0 ? s : 100), top(-1), stackPtr(new T[size] ){}/* コンストラクタ */
        ~Stack(){ delete [] stackPtr; }
        bool push(const T &);/* T型のデータをpush */
        bool pop(T &);/* T型のデータをpop */
        bool isEmpty() const { return top == -1;} /* スタックが空かどうか */
        bool isFull() const { return top == size - 1; }/* スタックが一杯かどうか */
        void print(){ cout << "スタックサイズ:" << size << endl;}
};

template <typename T>
bool Stack <T> :: push(const T &data){
    if (  !isFull() ){
            stackPtr[ ++top ] = data;/* スタックのトップにT型の新規割当てデータをセット */
            return true;
    }
    return false;
}

template <typename T>
bool Stack <T> :: pop(T& data){
    if ( !isEmpty() ){
        data = stackPtr[ top-- ];/* スタックの先頭要素の追い出し */
        return(true);
    }
    return false;
}
#endif
次に上で定義したStackに収容する要素例を考えます。通常のint, doubleなどの基本型が勿論ですが、クラスをStackにpush/popすることができます。そこで、ノードクラスを考えます。
Node.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* プログラム6-2 ノードのクラス*/
#ifndef _NODE
#define _NODE

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

class Node {
    string name; /* ノード名 */
    int value; /* ノードの値(ID) */
public:
    Node() : name(""), value(-1) {} /* 引数なしコンストラクタ:初期化はしておく */
    Node(string name, int id) : name(name), value(id) {}/* 引数をメンバ変数に代入するよう初期化 */
    string getName() { return name;}/* 名前を返す関数 */
    int getValue(){ return value; }/* 値を返す関数 */
    void printName(){ cout << "Node: " << name << endl;}
    void printvalue(){ cout << "Node: " << value << endl;}
};
#endif
最後に、StackにNodeクラスとdouble基本型をそれぞれpush/popする例を示します。
StackSampleMain.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* プログラム6-3 スタックの利用例 */

#include "Node.h"
#include "Stack.h"

Node week[] = {/* Node型のコンストラクタを並べて配列の要素とする */
    Node("Sunday", 0), 
    Node("Monday", 1), 
    Node("Tuesday", 2),
    Node("Wednesday", 3), 
    Node("Thursday", 4), 
    Node("Friday", 5), 
    Node("Saturday", 6)
};
double temperature[] ={
    32.5, 27.8, 30.6, 19.8, 8.0, 29.3
};
int main(){
    Stack <Node> s1(10);/* Node型変数を保持するスタック */
    int size1 = sizeof(week)/sizeof(week[0]);
    for (int i = 0 ; i < size1 ; i++) s1.push(week[i]);
    s1.print();/* スタックサイズのプリント */
    Node node;
    while (s1.pop(node)) {
        node.printName();
    }
    
    Stack <double> s2(20);/* double型変数を保持するスタック */
    int size2 = sizeof(temperature)/sizeof(temperature[0]);
    for (int i = 0 ; i < size2 ; i++) s2.push(temperature[i]);
    s2.print();/* スタックサイズのプリント */
    double value;
    while (s2.pop(value)) {
        cout << "気温:" << value << endl;
    }
    return 0;
}
以下がコンパイルと実行例です。

$ g++ -Wall -std=c++14 -o StackSampleMain StackSampleMain.cpp

$ ./StackSampleMain
スタックサイズ:10
Node: Saturday
Node: Friday
Node: Thursday
Node: Wednesday
Node: Tuesday
Node: Monday
Node: Sunday
スタックサイズ:20
気温:29.3
気温:8
気温:19.8
気温:30.6
気温:27.8
気温:32.5
テンプレート・コンストラクタならびに、 テンプレート・メンバー関数を使った別の例を紹介します。
MyClass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* プログラム6-4 テンプレート・コンストラクタ、テンプレート・メンバー関数、
	テンプレート・演算子オーバーロード */
#include <iostream>
#include <iomanip>
using namespace std;

template <typename T>
class MyClass {
  private:
    T value; /* T 型の値を保持 */
  public:
    MyClass() {} /* デフォルトコンストラクタ */
    template <typename X> /* テンプレート・コンストラクタ 型変換を勝手にしてくれる */
    MyClass(const MyClass<X> &x): value(x){}/* コピーコンストラクタ */
    template <typename X>/* テンプレート・メンバー関数:型が違っても代入可能となる */
    void assign( const MyClass<X>& x){
        value = x.getValue();
    }
    template <typename X>/* テンプレート・演算子のオーバーロード */
    void operator=(const MyClass<X> &x){
        value = x.getValue();
    }
    T getValue() const {/* T型の変数を返す */
        return value;
    }
    void print(){/* 変数を10桁の浮動小数点でプリントする */
        cout << setprecision(10) << scientific << "value = " << value << endl;
    }
    void printHex(){/* 変数を8桁の16進数でプリントする */
        cout << setprecision(8) << "value (16進数) = " << hex << value << endl;
    }
    void setValue(T value){/* 値をセットする */
        this->value = value;
    }
};
これを呼び出すmain関数例は以下のようです。
MyClass.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* プログラム6-5 テンプレート・メンバー関数などの利用例 */
#include "MyClass.h"

int main(){
    MyClass<double> d;/* double型のMyClass変数(オブジェクト)生成 */
    MyClass<float>f;/* float型のMyClass変数(オブジェクト)生成 */
    MyClass<int> i;/* int型のMyClass変数(オブジェクト)生成 */

    d.assign(d);/* double型にdouble型を代入 */
    d.setValue(1.932065470e05);/* 具体的な値を代入 */
    d.print();/* プリント */
    f = d;/* 代入演算子のオーバーロード:double型のMyClassをfloat型のMyClassに代入 */
    d.assign(i);/* double型にint型のMyClass変数をassign関数で代入 */
    d.setValue(8000000000);/* 80億を代入 */
    d.print();/* プリント */

    i.setValue(8000000000);/* 80億を代入 (オーバーフロー警告が出る!)*/
    i.print();/* プリント(桁あふれの結果、変な値がプリントされる) */
    i = f;/* int型のMyClass変数にfloat型のMyClass変数を代入:整数に切り捨てられる */
    i.print();/* プリント */

    f.setValue(-1.0F);/* -1.0を代入 */
    i = f;/* int型のMyClass変数にfloat型のMyClass変数を代入  */
    i.printHex(); /* 16進数でプリント (IEEE754浮動小数点表現形式での16進数がわかる)*/
    f.printHex(); /* 16進数でプリント */
    return 0;
}
以下がコンパイルと実行例です。
$ g++ -Wall -std=c++14 -o MyClass MyClass.cpp
MyClass.cpp: 関数 ‘int main()’ 内:
MyClass.cpp:50:24: 警告: 暗黙の定数変換でオーバーフローしました [-Woverflow]
   i.setValue(8000000000);/* 80億を代入 (オーバーフロー警告が出る!)*/
                        ^
$ ./MyClass
value = 1.9320654700e+05
value = 8.0000000000e+09
value = -589934592
value = 193206
value (16進数) = ffffffff
value (16進数) = -1.00000000e+00
この例で特徴的なのは、データ型が違う場合の代入が、 テンプレート・コンストラクタによるコピーコンストラクタ、 テンプレート・演算子のオーバーロードによる代入などで可能になることです。 一方で、プログラム6-5の17行目にあるように、 double型で表現できるデータも、int型に代入すると、 桁落ちなどが平気で起きるので注意が必要であることです。 22行目からのfloat型とint型でのデータの代入では、 いずれも4バイトで実装されているため、32ビットのIEEE浮動小数点表現の、 16進数での表現を確かめる例となっています。ここでは、-1.0であり、 これが32ビットでは、すべて2進数で1、すなわち21ビットでは、 FFFFFFFF (ffffffff)という8つの16進数となっています。

簡単なラムダ式

C++11から、ラムダ式が導入されました。以前はinline関数で、簡単な関数を書いたり、 C言語で見られた#defineなどのプリプロセッサを簡易関数の代わりに使ったりしていました。

auto minus = [](int x, int y){ return x - y;};
int result = minus(5, 2); // result = 3

ラムダ式のシンタックスは以下のようです。

[ captures ] (parameters) -> returnTypesDeclaration { lambdaStatements; }

[ captures ] (キャプチャ):キャプチャ句(ラムダ導入子)は、 ラムダ関数に使用できる外部変数と、 値(コピー)または参照によってキャプチャする必要があるかどうかを指定します。 キャプチャ句の存在でラムダ式の開始を識別することができます。 空のキャプチャ句[]は何も取得しないことを意味します。 この場合、ラムダ式本体は囲みスコープ内の変数にアクセスしません。

(parameters)(パラメータ):これはラムダ宣言子とも呼ばれるオプションのパラメータリストです。 ゼロ引数を取る関数が必要な場合は、パラメータリストを省略できます。

- > returnTypeDeclaration:これは戻り値の型です。 ほとんどの場合、コンパイラは、0または1つのreturn文があるときに ラムダ式の戻り値の型を推論することができます。 なので、この部分はなくてもOKです。 しかし、コードを理解しやすくするため、戻り値の型を指定することができます。

{lambdaStatements; }{ラムダ文:}:これはラムダボディです。 ラムダ本体内のステートメントは、キャプチャされた変数とパラメータにアクセスできます。

キャプチャ部分には、以下のような記法で記述します。
キャプチャ記法 説明
[&] デフォルトで環境にある変数を参照して、ラムダ式のなかで使用する
[=] デフォルトで環境にある変数をコピーして、ラムダ式のなかで使用する
[&x] 変数xを参照して、ラムダ式のなかで使用する
[x] 変数xをコピーして、ラムダ式のなかで使用する
[&, x] デフォルトで参照キャプチャ、変数xのみコピーして、ラムダ式のなかで使用する
[=, &x] デフォルトでコピーキャプチャ、変数xのみ参照して、ラムダ式のなかで使用する
[this] *thisのメンバを参照して、ラムダ式のなかで使用する
[this, x] *thisのメンバを参照し、変数xのみコピーして、ラムダ式のなかで使用する

非常に簡単なラムダ式でのHello worldをプリントする関数例は以下のようです。

#include <iostream>
using namespace std;

int main()
{
    auto func = [] () { cout << "Hello world"; };
    func(); // now call the function
}

インデックス付きソーティング事例

値がvector <float> values;にあり、これをインデックス付きでソーティングしたい場合、

#include <algorithm>
vector <float> values;/* ここにすでにソートしたい値が入っているとする */
vector <int> index(values.size(),0);/* インデックスをvectorで定義 */
for (auto  i = 0 ; i < (int)index.size() ; i++)  index[i] = i;
/* インデックス付き降順ソーティング例 */
sort( index.begin(), index.end(),
        [&](int a, int b) { return values[a] > values[b]; } );
と書けます。

STLの基礎

標準テンプレートライブラリ(Standard Template Library: STL)は、C++言語に標準的にサポートされている各種の機能の総称であり、基本的にどれも対応するヘッダーファイルにその定義がなされています。 したがって、「標準テンプレートライブラリを利用する」=「対応するSTLのヘッダーファイルをincludeし、その中で定義されているクラスや、関数、演算子、マクロなどを利用する」と言っても過言でありません。

STLは、C++の言語仕様に含まれるため、C++言語の処理系が違ったとしても基本的には、どの処理系でも同じ動作をすることが期待されています。 したがって、STLで書かれたコードは一般的には「可搬性がある」ということになっています。 しかし、STLを知っていて駆使できることと、可読性の高いプログラム、あるいは性能のよいプログラムを書けること、とは関係ありません。 つまり、STLを使うとしても、下手に使うと、遅く、読みにくいプログラムが書けてしまうので注意が必要です。 とはいえ、「こういうとき、可変長の配列があると便利」とか、 「こういうとき、ハッシュマップがあると便利」といった要求には、十分にこたえることができます。 また、可読性を向上させるために「コメントの多いプログラムを書くこと」を常に心がける点は、STLを使おうが、使うまいが関係ありません。 一般的には、STLを使うと、特殊な関数は反復子などの文法が入ってくるので可読性は低くなる、と覚えておくべきでしょう。たとえば、vector <double> v;と宣言したデータをpush_back関数などで追加した場合であっても、iteratorを使うより、普通のforループで配列的にアクセスするほうが、性能がよかったりします。

STLを大別すると、以下の3つのグループに分かれます。代表的な クラスを付随させています。

STLは現在も進化し続けています。実際、2011年に発表された国際標準 、いわゆるC++11 では、様々な拡張がなされています。 その中に <unordered_set>, <unordered_map>など、ハッシュ表も追加されました。

<vector>クラスのSTL使用例(復習)

すでに、何度も使っていますが、 おそくらSTLの中でもっとも頻繁に利用されるのが<vector>クラスだと思います。 以下は、STLの<vector>クラスで実装した例です。
myNameVector.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/* プログラム6-6: 可変長リストを<vector>クラスで実現する例 */
#include <iostream>
#include <vector>
using namespace std;

/* 学生の構造体 */
class Student {
public:
    int  gender;  /* 性別  0:男, 1:女 */
    string  id;   /* 学籍番号 */
    string name;  /* 氏名 */
    string bloodType; /* 血液型 */
    double weight;/* 体重 */
    double height;/* 身長 */
public:
    Student(){}
    Student(int gender, string id, string name, string bloodType,
        double weight, double height)
         :gender(gender),id(id), name(name), bloodType(bloodType),
        weight(weight), height(height) {}
    void printStudent(Student &student);
    void printName(vector <Student> head);
};

/* 個々の学生のプリント */
void Student::printStudent(Student &student){
    cout << (student.gender? "女" : "男")
        << "," << student.bloodType
        << "," << student.id
        << "," << student.name
        << "," << student.weight
        << "," << student.height ;
}

/* 線形リストの先頭からプリント */
void Student::printName(vector <Student> head){
    int n = head.size();
    for ( int i = 0 ; i < n ; i++ ){/* vectorを走査し先頭からプリント */
        cout << (i+1) << "番目のデータは";
        printStudent(head[i]);/* i番目のvector要素には配列のようにアクセス可 */
        cout << "です。" << endl;
    }
}

/* 線形リストをvector(可変長配列STL)で実装した例: メインプログラム */
int main(void){
    vector <Student> nameVector;
    Student Yamada(0, "B0001", "山田太郎", "A", 72.5, 170.0);
    Student Tanaka (0, "B0003", "田中二郎",  "AB", 55.9,168.6);
    Student Suzuki  (1, "B0009", "鈴木花子",  "O", 45.0, 157.2);
    Student Sato     (0, "B0015", "佐藤三郎", "B",  85.2, 180.2);
    nameVector.push_back(Yamada);/* 末尾に山田さんを追加 */
    nameVector.push_back(Tanaka);/* 末尾に田中さんを追加 */
    nameVector.push_back(Suzuki);/* 末尾に鈴木さんを追加 */
    nameVector.push_back(Sato);/* 末尾に佐藤さんを追加 */
    Student *head = new Student();
    head->printName(nameVector);/* 先頭からプリント */
    return 0;
}
以下がコンパイルと実行例です。

$ g++ -Wall -std=c++14 -o myNameVector myNameVector.cpp

$ ./myNameVector
1番目のデータは男,A,B0001,山田太郎,72.5,170です。
2番目のデータは男,AB,B0003,田中二郎,55.9,168.6です。
3番目のデータは女,O,B0009,鈴木花子,45,157.2です。
4番目のデータは男,B,B0015,佐藤三郎,85.2,180.2です。

Vectorのサイズと初期値

vector型は、 任意の型をテンプレートとして持つことができる動的な配列のようなもの、 として利用できる点がもっとも便利なところです。 一方で、サイズと初期値を持ちます。以下の例を通して考えて見ます。
vector <int> v1 = {1,2,3,4,5};
vector <string> v2;
vector <Shape*> v2(45);
vector <double> v4(25,3.14159265);
最初の例は、int型のvectorでサイズが5、初期値はv1[0] = 1, v1[1] = 2, v1[2] = 3, v1[3] = 4, v1[4] = 5である。 第2の例はstring型のvectorでサイズは0を意味します。 このような場合、たとえばpush_backのような関数で要素を追加しない限り、 何らの値も入っていなので注意してください。 第3の例は、Shapeへのポインタ型のvectorで、 サイズは45、初期値はnullptrとなります。 第4の例は、double型のvectorでサイズが25、 初期値はすべて3.14159265です。

Vectorの利点

vector型には以下のアドバンテージがあります。


グラフと深さ優先探索

第2回の資料で、グラフのノード(頂点)とエッジ、ならびにダイクストラのアルゴリズムを紹介しました。 ここでは、より基本的なグラフのデータ構造を示したいと思います。ここでは、<vector>クラスを使った 隣接リストの実装例を紹介します。



ここでは、有向グラフが与えられたとき、深さ優先探索を再帰的に行うアルゴリズムを紹介します。 まず、Graphクラスを以下のように実装してみます。
Graph.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* プログラム6-7 グラフのクラス*/
#ifndef _GRAPH
#define _GRAPH
#include <cstdio> 
#include <iostream> 
#include <vector> 
#include <limits>
using namespace std; 

class Graph {
public:
    enum colors {BLACK, WHITE, GRAY}; 
    int *color;/* 色をフラグとして利用 */
    int *p;/* predecessor (DAGの直前のノード)の保持 */
    int numV;/* number of vertices */
    int numE;/* number of edges */
    int time;
    int *discoveryTime;/* DFSの開始時刻 */
    int *finishTime;/* DFSの終了時刻 */
    vector <int> *adjList; /* 隣接リスト */

    Graph(int vertex, int edge) : numV(vertex), numE(edge){/* コンストラクタ */
        color = new int[numV];/* 色 */
        p = new int[numV];/* 直前のノード:現在対象としているノードを指すノード */
        discoveryTime = new int[numV];/* 処理開始時刻 */
        finishTime = new int[numV];/* 処理終了時刻 */
        time = 0;
        adjList = new vector <int>[numV];/* 隣接リスト用メモリ割当 */
    }
    vector <int> *getAdjList(){ return adjList; }
    void DFS(vector <int> *); /* Depth-First Search */
    void DFS_VISIT(vector <int> *, int ); /* DFSで再帰的にコールする関数 */
};
#endif
Graph.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* プログラム6-8 グラフのクラスのメンバー関数*/
#include "Graph.h"

void Graph::
DFS_VISIT( vector <int> *G, int u ) { 
    time = time + 1; 
    discoveryTime[u] = time; /* 深さ優先探索、その方向での作業開始 */
    color[u] = GRAY; /* 発見時はGRAYに */
    int size = G[u].size();
    for ( int v = 0 ; v < size ; v++ ) { 
        if (color[G[u][v]] == WHITE) { /* まだ未訪問 */
            p[G[u][v]] = u; /* 直前のノードに保持 */
            DFS_VISIT( G, G[u][v] ); /* 深さ方向に再帰的にコール */
        }
    }
    color[u] = BLACK; /* 終了時はBLACKに */
    time = time + 1; 
    finishTime[u] = time; /* 深さ優先探索、その方向での作業終了 */
} 

void Graph::
DFS ( vector <int> *G ) { 
    for ( int u = 0 ; u < numV ; u++) { 
        color[u] = WHITE; /* 未訪問ノードはWHITEにしておく */
        p[u] = numeric_limits <int>::min(); /* 最小値を適当に代入 */
    } 
    time = 0; /* 時刻のリセット */
    for ( int u = 0 ; u < numV ; u++) { 
        if (color[u] == WHITE) { 
            DFS_VISIT( G, u );  /* 深さ優先探索開始 */
        } 
    } 
}
Graph.cppの25行目にあるnumeric_limitsは, テンプレートの一種で、いろいろなデータ型で、min(), max()のような最小値、最大値を与えられるほか、 マシンエプシロンを表すepsilon()関数や無限大を表すinfinity()などがあります。 このため、Graph.hの7行目にあるように、#include <limits>を宣言しておく必要があります。 たとえば、

numeric_limits <long double>::max()

とすれば、long double型での最大値を与えることができます。
DFSMain.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* プログラム6-9 グラフの深さ優先探索開始*/
#include "Graph.h"

int main(int ac, char **av) 
{ 
    int numV=5, numE=9;
    Graph graph(numV, numE);/* グラフのオブジェクト作成 */
    vector <int> *adjList = graph.getAdjList(); /* グラフの隣接リスト */
    int u, v; 
    int EdgeList[][2] = {{0,1},{0,2},{1,4},{2,1},{2,4},{3,0},{3,2},{3,4},{4,4}}; 
    string name[] = {"A", "B", "C", "D", "E"};
    for (int i = 0 ; i < numE ; i++){
        u = EdgeList[i][0];/* エッジの開始 */
        v = EdgeList[i][1];/* エッジの終端 */
        adjList[u].push_back(v);/* uからvにエッジ */ 
    }
    graph.DFS(adjList);/* 深さ優先探索 */

    for (int i = 0 ; i < numV ; i++){/* DFSの結果プリント */
        cout << "[" << i << "] : " << name[i] << " d = " << graph.discoveryTime[i] <<
        "  f = " << graph.finishTime[i] << endl;
    }
    return 0; 
} 
以下がMakefile例です。

#
# Makefile ソフトウェア演習(アドバンスコース)
#
# $ make コンパイルして実行モジュールを作成
# $ make clean オブジェクトファイルを削除
# 
#
# for C++ define  CC = g++
C++ = g++
CFLAGS  = -Wall -std=c++14
OFILES = DFSMain.o Graph.o
default: DFSMain

DFSMain:$(OFILES) 
	$(C++) $(CFLAGS) -o DFSMain $(OFILES)

# 依存関係
Graph.o:  Graph.h Graph.cpp
	$(C++) $(CFLAGS) -c Graph.cpp
DFSMain.o:  Graph.h Graph.cpp DFSMain.cpp
	$(C++) $(CFLAGS) -c DFSMain.cpp
#
clean: 
	$(RM) *.o
以下がコンパイルと実行例です。
$ make
g++ -Wall -std=c++14 -c DFSMain.cpp
g++ -Wall -std=c++14 -c Graph.cpp
g++ -Wall -std=c++14 -o DFSMain DFSMain.o Graph.o

$ ./DFSMain
[0] : A d = 1  f = 8
[1] : B d = 2  f = 5
[2] : C d = 6  f = 7
[3] : D d = 9  f = 10
[4] : E d = 3  f = 4
結果の意味としては、f(finishTime)(小さいほうが処理が早く終わったことを表す)より、 Eが深さ優先では、最も早く処理が終わり、 次いで、B, C, Aで最後にDの処理が終わったことを示しています。


例外処理

例外処理(Exception Handling)は、オブジェクト指向言語に特徴的というよりも、80年代以降に登場したほとんどの言語に備わっている機能です。 Java言語では、例外処理なしでは、ファイルの入出力を含むプログラムは全く書けないようになっています。 C++言語でも、Java言語ほどではないですが例外処理を書けるための予約語、構文が標準で備わっています。 実際に使用する予約語としては、 try, catch, throwなどが代表的な例外処理の予約語です。
C++言語には、標準機能としてexceptionという例外処理用のクラスが備わっています。これを使うことが例外処理の必要条件ではありませんが、とても便利なので利用することをすすめます。 その際、以下のように、

#include <exception>

のinclude文をつける必要があります。

ここで、クラスがどう定義されているかを紹介しておきます。 これなしでは、あとで出てくるwhat()関数がとても唐突になるからです。


class exception {
public:
    exception() throw();
    exception(const exception&) throw();
    exception& operator=(const exception&) throw();
    virtual ~exception() throw();
    virtual const char* what() const throw();
private:
    //...
};

一般的に、C++言語での典型的な例外処理の使用法は以下のようにtry~catch ブロック構成をとります。

try { 
	// 例外をthrowする関数等の呼び出し
} 
catch ( MyException  &me ) {// me.whatまたはme.interesting_valueを利用 }
catch ( runtime_error &re ){// re.whatを利用 }
catch ( exception &e ){ // e.what()を利用 }
catch (...){ // local cleanup }


意味としては、tryブロックで例外が起こるかもしれない関数呼出しやクラスの生成、メモリ割当、入出力命令などを実行します。 その際、これらの呼び出される関数側で例外が発生したときにある例外を投げる(throwする)処理が設定されていることが前提となります。 そして、例外が本当に発生したらcatchブロック(複数ありえます)で例外を受け止め、その場合の処理を実施する、という流れです。 例外処理を正しく実装することで、一般的にデバッグが簡素化され、 実行時のエラーもキャッチできれば、ABEND (Abnormal End)、すなわち異常終了したときに、わけがわからない、という事態を減らすことができると期待されています。 習うより慣れよ、ということで例を示します。 この例では、ゼロ割算を見つけて警告しようというものです。

ZeroDivideCheck.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* プログラム6-10 ゼロ割算チェックを例外処理でチェックするサンプル */
#include <iostream>
using namespace std;
#include <exception>

/* 例外処理クラスをexceptionの派生クラスとして定義 */
class DivideByZeroException : public exception {
    public:
    virtual const char *what() const throw() {
        return("ゼロ割算をしようとしました。");
     }
};
/* 例外をthrowする関数例:割算 */
double quotient( int numerator , int denominator ){

    if ( denominator == 0)
        throw DivideByZeroException();

    return (double)( numerator ) / (double) denominator;
}

int main(){
    int number1;
    int number2;
    double result;

    cout << "2つの整数を入力してください: ";
    while ( cin >> number1 >> number2 ) {
        
        try {
            result = quotient( number1, number2 );
            cout << "商は: " << result << "です。" << endl;
        }
        catch ( DivideByZeroException &e ){
            cout << e.what() << endl;
        }
    }
    cout << endl;
    return 0;
}
この例でわかるように、 exceptionクラスを継承して生成しているDivideByZeroExceptionクラスは、 仮想関数として常にwhat()という関数が用意されています。 what()関数では、 文字列を返すという形態をとり、 呼び出し側のキャッチ・ブロックでは、 例外を受け取る参照型変数のwhat関数をe.what()という形で呼び出していることがわかります。

$ g++ -Wall -std=c++14 -o ZeroDivideCheck ZeroDivideCheck.cpp

$ ./ZeroDivideCheck
2つの整数を入力してください: -4 1
商は: -4です。
3 2
商は: 1.5です。
2 7
商は: 0.285714です。
1 0
ゼロ割算をしようとしました。

第2の例は、整数を入力し、これが確かに整数であり、 インデックスとして、ある範囲にある値かどうか をチェックするプログラム例です。

MyExceptionHandler.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/* プログラム6-11 入力文字列の正しさとインデックスの範囲を例外処理でチェックするサンプル */
#include <iostream>
#include <exception>
#include <sstream>
#include <climits>
using namespace std;

/* 例外処理のクラスたち */
class IndexOverFlow : public exception { 
 public:
    virtual const char *what() const throw() {    
        return("インデックスがオーバーフローしました。"); 
    }
};

class IndexUnderFlow : public exception { 
 public:
    virtual const char *what() const throw() {    
        return("インデックスがアンダーフローしました。"); 
    }
};

class BadIntegerFormatException : public exception { 
 public:
    virtual const char *what() const throw() {
        return("整数に変換できない文字列です。"); 
    }
};

/* 文字列が(INT_MAX以外の)整数かどうかチェックする関数例 */
int CanBeIntFromString( string & s ) {
    int integer = INT_MAX;
    istringstream iss( s );
    iss >> integer;/* 入力文字列が整数ではじまるときだけintergerが書き換わる */
    ostringstream oss;
    oss << integer;
    string os = oss.str();
    /* 書き換わっていないか、整数ではじまるがあとに変な文字があり長さが変化する場合例外を投げる */
    if (integer == INT_MAX || s.length() != os.length()) 
    throw BadIntegerFormatException();
    return(1);
}

/* 整数が、インデックスとしてある上限、下限内にあるとき、例外を投げる関数例 */
#define MAX_INDEX (30000)
int SafeIndex( int x ){
    if (x < 0)
        throw IndexUnderFlow();
    else if (x > MAX_INDEX)
        throw IndexOverFlow();
    else
        return(1);
}

/* main関数 */
int main(){
    try {
        int a;
        string s;
        stringstream ss;
        cout << "整数を入力してください。\n入力整数:";
        cin >> s;/* 入力チェックのため、まず文字列で読込む */
        CanBeIntFromString(s);
        ss << s;/* 整数に変換する準備 */
        ss >> a;/* 整数に変換 */
        SafeIndex(a);/* 入力整数の範囲をチェック */
    }
    catch (BadIntegerFormatException &e) {/* 文字列の例外をキャッチ */
        cout << e.what() << endl;
        return 1;
    }
    catch (IndexOverFlow &e) {/* IndexOverFlow型の例外はここでキャッチ */
        cout << e.what() << endl;
        return 2;
    }
    catch (IndexUnderFlow &e) {/* IndexUnderFlow型の例外はここでキャッチ */
        cout << e.what() << endl;
        return 3;
    }
    cout << "正しい整数が入力されました。" << endl;
    return 0;
}
この例では、複数のキャッチ・ブロックで3種類の例外処理を行っていることに注意してください。 それぞれ、整数のはずの文字列に整数以外が入力された時の例外処理、 その整数値のアンダーフローとオーバーフロー例外のキャッチを行っています。実行結果は以下のようです。

$ g++ -Wall -std=c++14 -o MyExceptionhandler MyExceptionhandler.cpp

$ ./MyExceptionhandler
整数を入力してください。
入力整数:2
正しい整数が入力されました。

$ ./MyExceptionhandler
整数を入力してください。
入力整数:-35
インデックスがアンダーフローしました。

$ ./MyExceptionhandler
整数を入力してください。
入力整数:35.5
整数に変換できない文字列です。

$ ./MyExceptionhandler
整数を入力してください。
入力整数:35890
インデックスがオーバーフローしました。

例外処理のもうひとつの例として、動的なメモリの割当で、必要とするメモリが確保できない場合の例外処理プログラムを参考までに紹介します。 まとめて、これは標準ライブラリでの例外で代表的には以下の例外があります。
C++言語の標準ライブラリの例外 Throwされる例外
stringlength_error, out_of_range
vectorlength_error, out_of_range
new Tbad_alloc, bad_array_new_length
typeid()bad_typeid
regexregex_error
iostreamios_base::failure
threadsystem_error
MemoryCheck.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* プログラム6-12 メモリ割当例外処理例 */
#include <new>
#include <iostream>
using namespace std;
#include "Matrix.h"

int main()
{
    int size;/* 配列の要素数 */
    cout << "要素数:";
    cin >> size;
    cout << size << "がサイズとして入力されました" << endl;
    int* A;
    Matrix* M;
    try {
        A = new int[size];    /* 動的メモリ割当 */
        M = new Matrix(size, size);
        for (int i = 0; i < size; i++)
            A[i] = i;
    }
    /* メモリあふれは、#include <new>で定義されているbad_allocクラスでキャッチ */
    catch (bad_alloc) {
        cout << "メモリがオーバーフローしました。\n";
        return 1;
    }

    delete[] A;    /* メモリの破棄 */
    delete M;

    cout << "正常にメモリ確保できました。" << endl;
    return 0;
}
なお、メモリ割当に対する結果は、同じ(GNUの)g++言語処理系であってもOSの違い等で、振舞いが異なりますので注意してください。 演習室のDarwinでは、たとえば以下のような実行結果が得られます。
im170:6 ma002$ g++ --version
g++-4.9 (Homebrew gcc 4.9.2_1) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

im170:6 ma002$ g++ -Wall -std=c++14 -o MemoryCheck MemoryCheck.cpp Matrix.cpp

im170:6 ma002$ ./MemoryCheck
要素数:1000
1000がサイズとして入力されました
正常にメモリ確保できました。

im170:6 ma002$ ./MemoryCheck
要素数:100000000000000000
2147483647がサイズとして入力されました
MemoryCheck(85433,0x7fff7796f300) malloc: *** mach_vm_map(size=17179869184) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
メモリがオーバーフローしました。
一方、Ubuntu (Linux)系のg++では、以下のように、システムからのエラー表示はなく、プログラムだけで制御でき、以下のような結果となります。
aono@gpu04:~/C++/6$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

aono@gpu04:~/C++/6$ g++ -Wall -std=c++14 -o MemoryCheck MemoryCheck.cpp Matrix.cpp

aono@gpu04:~/C++/6$ ./MemoryCheck
要素数:1000
1000がサイズとして入力されました
正常にメモリ確保できました。

aono@gpu04:~/C++/6$ ./MemoryCheck
要素数:10000000000000000000000
2147483647がサイズとして入力されました
メモリがオーバーフローしました。
同様に、Windows(cygwin)系のg++では、以下のように、システムからのエラー表示はなく、プログラムだけで制御でき、以下のような結果となります。
$ g++ --version
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ -Wall -std=c++14 -o MemoryCheck MemoryCheck.cpp Matrix.cpp

$ ./MemoryCheck
要素数:10000
10000がサイズとして入力されました
正常にメモリ確保できました。

$ ./MemoryCheck
要素数:100000000000
2147483647がサイズとして入力されました
メモリがオーバーフローしました。
ちなみに、Java言語で類似の動的メモリ割当例外処理は、たとえば以下のように書けます。
MemoryCheck.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* プログラム6-13 メモリ割当例外処理例(Java言語) */
public class MemoryCheck {
    /* Javaでのメモリ例外エラーチェック サンプル */
    public static void main(String[] args){
        try {
            int size = Integer.valueOf(args[0]).intValue();/* コマンドラインから第一引数を整数として読込み */
            double matrix[][] = new double[size][size];/* 2次元配列(連続領域)を動的に生成 */
        }
        catch (OutOfMemoryError e){/* OutOfMemoryErrorクラス(システムクラス)でキャッチ */
            System.out.println("メモリがオーバーフローしました。");
            return;
        }
        catch (NumberFormatException e){
		System.out.println("入力された数字が整数ではないか、整数範囲を逸脱しています。");
		return;
	}
        System.out.println("正常にメモリ確保できました。");
    }
}
Java言語(UTF-8エンコーディング)でのコンパイル と実行結果は以下のようです。
コンパイルでは、-encoding "UTF-8"、または -encoding UTF-8(ダブルクオートはなくてもいい) を、実行では、-Dfile.encoding=UTF-8をオプションでつける必要があります。
$ javac -J-Dfile.encoding=utf-8 MemoryCheck.java

$ java -Dfile.encoding=utf-8 MemoryCheck 1000
正常にメモリ確保できました。

$ java -Dfile.encoding=utf-8 MemoryCheck 100000000000
入力された数字が整数ではないか、整数範囲を逸脱しています。

$ java -Dfile.encoding=utf-8 MemoryCheck 100000000
メモリがオーバーフローしました。

C++11での例外処理

C++11では、以下のように、noexceptというキーワードが導入されました。

    int f(int x) throw();  //C++98(最適化しにくい)
    int f(int x) noexcept; //C++11(最適化しやすい)
    int f(int x); //(最適化しにくい)
このnoexceptは文字通り、その関数では例外は発生しない、 という宣言です。 この宣言をすると、実行時のスタックを保持しなくてよいため、 コンパイラは最適化しやすくなります。 一方、throw()があると最適化しにくいことが知られています。 上の第3の例のように、何もつかない関数はthrows()があるものと同様、 最適化しにくいことが知られています。

vector型での範囲チェック

vector型に関して、すでに幾つかの事例で紹介してきましたが、 例外処理の観点から、vector型の変数に配列的なアクセスを行う場合、 例外をキャッチできません。 これを回避するには、vector型のat()関数を使うことで出来ます。 そこで、演算子のオーバーローディングで、 配列的なアクセス演算でも例外処理をできるようなクラスを作る例を紹介します。
Vec.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
/*プログラム6-14 配列的アクセスで例外を発生可能なVecクラス例 */
#include <iostream>
using namespace std;
#include <vector>
#include <stdexcept>

template <typename T>
class Vec: public vector <T> {
public:
  using vector <T>::vector; //vectorのコンストラクタを使う
  T & operator [](int i){ /* out_of_range例外発生可能な演算子 */
  	return vector <T>::at(i);
  }
  const T& operator [](int i) const{/* out_of_range例外発生可能な演算子 */ 
  	return vector <T>::at(i);
  }
};
Vecクラスを用いた例外処理例は以下のようです。
VecTest.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
/*プログラム6-15 Vecクラスでの例外キャッチ例 */
#include "Vec.h"

int main(){
	Vec <double> data { 1.0, 2.0, 3.0, 4.0, 5.0};
	try {
		data[0] -= 2;
		data[5] = 0.0;//ここで例外発生
	}
	catch (out_of_range){
		cerr << "range error" << endl;
	}
	catch (...){/* すべての例外をキャッチ */
		cerr << "unknown exception thrown" << endl;
	}
	return 0;
}
実行結果は以下のようです。

$ g++ -Wall -std=c++14 -o VecTest VecTest.cpp

$ ./VecTest
range error

ベイズ学習例(クラス継承の応用例)

動物を7つのクラスに分類する問題を考えます。 最終的に、この問題を以下の図に示すように、基底クラス(Animal)と、 7つの派生クラス(Mammal(哺乳類)クラス、 Reptile(爬虫類)、Amphibian(両生類)、Bird(鳥類)、 Fish(魚類)、Insect(昆虫)、Invertebrate(無脊椎動物))に分けて、 クラスの継承を利用しながら、分類していきます。 なお、昆虫は無脊椎動物の一種ですが、別に扱います。



今、100種類程度のいろいろな動物のデータが与えられたとします。 各動物には、名前と以下の16種類の属性データが付随しています。 足の数以外はすべてブーレアン変数です。 ただし、足の数もデータとしては0, 2, 4, 6, 8それ以外のように考えて、 ブーレアンとして扱うことにします。

具体的には、このデータは、 データマイニングや機械学習のレポジトリとして有名なUCI(米国カリフォルニア大学アーバイン校)のデータセットに含まれる、 Zooデータが元になっています。 このデータをサーバにCSVファイルとして置いています。

さて、このようなブーレアン型の複数のデータが与えられたとき、 ここで例示するプログラムでは、上記のファイルを 訓練データとして訓練し、 未知データに直面したとき、 訓練した「モデル」から予測することとします。 この事例での特徴は、属性がブーレアン、すなわち、「離散データ」であることです。 このような離散データに有効な学習器として、 ナイーブベイズ学習器を実装することにします。

まず、16種類の属性を保持するAttribute.hという名前のヘッダーファイルを作ります。
Attribute.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* プログラム6-16 動物の属性クラス */
#ifndef _ATTRIBUTE
#define _ATTRIBUTE
#include <iostream>
using namespace std;

class Attribute {
    public:
        string name;/* 動物の名前 */
        bool hasHair;/* 髪(毛)があるか */
        bool hasFeather;/* 羽があるか */
        bool hasEgg;/* 卵から生まれるか */
        bool hasMilk; /* 母乳があるか */
        bool isAirborne; /* 大気中に飛んでいられるか (canFly) */
        bool isAquatic; /* 水中で生きていけるか */
        bool isPredator; /* 捕食者か */
        bool isToothed; /* 歯があるか */
        bool hasBackbone; /* 背骨があるか */
        bool doesBreathe;/* 呼吸するか */
        bool isVenomous;/* 毒があるか */
        bool hasFin;/* ひれがあるか */
        bool numLegs[6]; /* 足の数: 0, 2, 4, 6, 8, otherwise */
        bool hasTail; /* しっぽがあるか */
        bool isDomestic;/* ペットにできるか */
        bool isCatsize; /* 猫と同程度のサイズか */
    public:
        void init(){
            name = "";
            hasHair = hasFeather = hasEgg = hasMilk = isAirborne = false;
            isAquatic = isPredator = isToothed = hasBackbone = false;
            doesBreathe = isVenomous = hasFin = hasTail = false;
            isDomestic = isCatsize = false;
            for (int i = 0 ; i < 6 ; i++) numLegs[i] = false;
        }
        Attribute(){ init();    }/* コンストラクタ */
        Attribute(string name, bool hasHair, bool hasFeather, bool hasEgg,
            bool hasMilk, bool isAirborne, bool isAquatic, bool isPredator,
            bool isToothed, bool hasBackbone, bool doesBreathe, bool isVenomous,
            bool hasFin, bool numLegs[6], bool hasTail, bool isDomestic, bool isCatsize):
            name(name), hasHair(hasHair), hasFeather(hasFeather), hasEgg(hasEgg),
            hasMilk(hasMilk), isAirborne(isAirborne), isAquatic(isAquatic), 
            isPredator(isPredator), isToothed(isToothed), hasBackbone(hasBackbone),
            doesBreathe(doesBreathe), isVenomous(isVenomous), hasFin(hasFin),
            hasTail(hasTail), isDomestic(isDomestic), isCatsize(isCatsize){/* コンストラクタ */
                for (int i = 0 ; i < 6 ; i++) this->numLegs[i] = numLegs[i];
        }
        Attribute(const Attribute &a){/* コピーコンストラクタ */
            name = a.name;    hasHair = a.hasHair; hasFeather = a.hasFeather;
            hasEgg = a.hasEgg; hasMilk  = a.hasMilk; isAirborne = a.isAirborne;
            isAquatic = a.isAquatic; isPredator = a.isPredator; 
            isToothed = a.isToothed; hasBackbone = a.hasBackbone; 
            doesBreathe = a.doesBreathe; isVenomous = a.isVenomous; 
            hasFin = a.hasFin; hasTail = a.hasTail;
            isDomestic = a.isDomestic; isCatsize = a.isCatsize;
            for (int i = 0 ; i < 6 ; i++) numLegs[i] = a.numLegs[i]; 
        }
        string getName(){ return name; }
        void print(){
            cout << "名前 = " << name << endl;
            cout << "\t毛 = " << (hasHair?"true":"false") << endl;
            cout << "\t羽 = " << (hasFeather?"true":"false") << endl;
            cout << "\t卵 = " << (hasEgg?"true":"false") << endl;
            cout << "\t母乳 = " << (hasMilk?"true":"false") << endl;
            cout << "\t飛行 = " << (isAirborne?"true":"false") << endl;
            cout << "\t水生 = " << (isAquatic?"true":"false") << endl;
            cout << "\t肉食 = " << (isPredator?"true":"false") << endl;
            cout << "\t歯 = " << (isToothed?"true":"false") << endl;
            cout << "\t背骨 = " << (hasBackbone?"true":"false") << endl;
            cout << "\t呼吸 = " << (doesBreathe?"true":"false") << endl;
            cout << "\t毒 = " << (isVenomous?"true":"false") << endl;
            cout << "\tひれ = " << (hasFin?"true":"false") << endl;
            cout << "\tしっぽ = " << (hasTail?"true":"false") << endl;
            cout << "\t家畜・ペット = " << (isDomestic?"true":"false") << endl;
            cout << "\t猫と同サイズ = " << (isCatsize?"true":"false") << endl;
            for (int i = 0 ; i < 6 ; i++)
            if (numLegs[i])
                cout << "\t足の数 = " << (2*i) << endl;
        }
};
#endif
次に基底クラスとなる、Animal.hを示します。
Animal.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* プログラム6-17 動物の基底クラス */
#ifndef _ANIMAL
#define _ANIMAL
#include <iostream>
using namespace std;
#include "Attribute.h"

class Animal {
    private:
        int chromosome; /* 染色体数 */
        double weight; /* 平均体重 */
        double size; /* 平均サイズ */
    protected: /* 派生クラスからアクセス可、他クラスやmainからは不可 */
        Attribute atr;/* 属性情報 */
    public:
        Animal(){ atr.init(); }/* コンストラクタ */
        Animal(string name){ atr.init(); atr.name = name; }/*コンストラクタ */
        Animal(const Attribute &a){/* 属性部分のコピーコンストラクタ */
            atr.name = a.name;    
            atr.hasHair = a.hasHair; /* 髪(毛)があるか */
            atr.hasFeather = a.hasFeather;/* 羽があるか */
            atr.hasEgg = a.hasEgg; /* 卵から生まれるか */
            atr.hasMilk  = a.hasMilk; /* 母乳があるか */
            atr.isAirborne = a.isAirborne;/* 大気中に飛んでいられるか (canFly) */
            atr.isAquatic = a.isAquatic; /* 水中で生きていけるか */
            atr.isPredator = a.isPredator;  /* 捕食者か */
            atr.isToothed = a.isToothed; /* 歯があるか */
            atr.hasBackbone = a.hasBackbone; /* 背骨があるか */
            atr.doesBreathe = a.doesBreathe; /* 呼吸するか */
            atr.isVenomous = a.isVenomous; /* 毒があるか */
            atr.hasFin = a.hasFin; /* ひれがあるか */
            for (int i = 0 ; i < 6 ; i++) atr.numLegs[i] = a.numLegs[i]; /* 足の数 */
            atr.hasTail = a.hasTail;/* しっぽがあるか */
            atr.isDomestic = a.isDomestic; /* ペットにできるか */
            atr.isCatsize = a.isCatsize;/* 猫と同程度のサイズか */
        }/* コピーコンストラクタ */
        virtual ~Animal(){}/* デストラクタ */
        virtual void print()=0;/* 仮想関数:動物をプリント */
        double getWeight();/* 平均体重を返す関数 */
        Attribute *getA(){ return &(this->atr); }/* 属性のポインタを返す関数 */
};
#endif
次に派生クラスを示します。Mammal.hからInvertebrate.hまで7種類あります。
Mammal.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* プログラム6-18 哺乳類(派生)クラス */
#ifndef _MAMMAL
#define _MAMMAL
#include <iostream>
using namespace std;
#include "Animal.h"

class Mammal: public Animal {
    private:
        bool inZoo; /* 動物園にいるか */
        bool isPet; /* ペットや家畜になるか */
    public:
        Mammal(){}/* コンストラクタ */
        Mammal(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Mammal(){}/* デストラクタ */
        void print(){
            cout << "Mammal:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setInZoo(bool inZoo){ this->inZoo = inZoo; }
        void setIsPet(bool isPet){ this->isPet = isPet; }
        bool getInZoo(){ return inZoo; }
        bool getIsPet(){ return isPet; }
};
#endif
Reptile.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* プログラム6-19 爬虫類(派生)クラス */
#ifndef _REPTILE
#define _REPTILE
#include <iostream>
using namespace std;
#include "Animal.h"

class Reptile: public Animal {
    private:
        bool isAmniote; /* 4足の爬虫類 */
    public:
        Reptile(){}/* コンストラクタ */
        Reptile(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Reptile(){}/* デストラクタ */
        void print(){
            cout << "Reptile:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setIsAmniote(bool isAmniote){ this->isAmniote = isAmniote; }
        bool getIsAmniote(){ return isAmniote; }
};
#endif
Amphibian.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* プログラム6-20 両生類(派生)クラス */
#ifndef _AMPHIBIAN
#define _AMPHIBIAN
#include 
using namespace std;
#include "Animal.h"

class Amphibian: public Animal {
    private:
        bool isArboreal; /* 樹木に住む */
    public:
        Amphibian(){}/* コンストラクタ */
        Amphibian(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Amphibian(){}/* デストラクタ */
        void print(){
            cout << "Amphibian:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setIsArboreal(bool isArboreal){ this->isArboreal = isArboreal; }
        bool getIsArboreal(){ return isArboreal; }
};
#endif
Bird.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* プログラム6-21 鳥類(派生)クラス */
#ifndef _BIRD
#define _BIRD
#include 
using namespace std;
#include "Animal.h"

class Bird: public Animal {
    private:
        bool isMigratory; /* 渡り鳥かどうか */
    public:
        Bird(){}/* コンストラクタ */
        Bird(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Bird(){}/* デストラクタ */
        void print(){
            cout << "Bird:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setIsMigratory(bool isMigratory){ this->isMigratory = isMigratory; }
        bool getIsMigratory(){ return isMigratory; }
};
#endif
Fish.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* プログラム6-22 魚類(派生)クラス */
#ifndef _FISH
#define _FISH
#include 
using namespace std;
#include "Animal.h"

class Fish: public Animal {
    private:
        bool isAbyssal; /* 深海魚かどうか */
    public:
        Fish(){}/* コンストラクタ */
        Fish(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Fish(){}/* デストラクタ */
        void print(){
            cout << "Fish:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setIsAbyssal(bool isAbyssal){ this->isAbyssal = isAbyssal; }
        bool getIsAbyssal(){ return isAbyssal; }
};
#endif
Insect.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* プログラム6-23 昆虫(派生)クラス */
#ifndef _INSECT
#define _INSECT
#include 
using namespace std;
#include "Animal.h"

class Insect: public Animal {
    private:
        bool isColeoptera; /* 甲虫類かどうか */
    public:
        Insect(){}/* コンストラクタ */
        Insect(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Insect(){}/* デストラクタ */
        void print(){
            cout << "Insect:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setIsColeoptera(bool isColeoptera){ this->isColeoptera = isColeoptera; }
        bool getIsColeoptera(){ return isColeoptera; }
};
#endif
Invertebrate.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* プログラム6-24 無脊椎動物(派生)クラス */
#ifndef _INVERTEBRATE
#define _INVERTEBRATE
#include 
using namespace std;
#include "Animal.h"

class Invertebrate: public Animal {
    private:
        bool isSpider; /* 蜘蛛かどうか */
        bool isScorpion; /* さそりかどうか */
        bool isCrab; /* カニかどうか */
        bool isSnail; /* かたつむりかどうか */
    public:
        Invertebrate(){}/* コンストラクタ */
        Invertebrate(const Attribute &a): Animal(a){}/*コンストラクタ */
        virtual ~Invertebrate(){}/* デストラクタ */
        void print(){
            cout << "Invertebrate:" << atr.name << endl;/* 仮想関数:動物をプリント */
        }
        void setIsSpider(bool isSpider){ this->isSpider = isSpider; }
        void setIsScorpion(bool isScorpion){ this->isScorpion = isScorpion; }
        void setIsCrab(bool isCrab){ this->isCrab = isCrab; }
        void setIsSnail(bool isSnail){ this->isSnail = isSnail; }
        bool getIsSpider(){ return isSpider; }
        bool getIsScorpion(){ return isScorpion; }
        bool getIsCrab(){ return isCrab; }
        bool getIsSnail(){ return isSnail; }
};
#endif

さて、ナイーブベイズ学習器で、クラスを推定する際、ベイズの定理を利用します。 動物( d )が与えられたとき、 それが属するクラス( c )の確率が最も大きくなるクラスを見出すことになります。 しかし、これは条件付確率で、( P ( c | d ) )と表せますが、 これを直接求めることは一般に難しいので、 ベイズの定理を用いて、クラスごとの出現確率( P ( c ) )と、 クラスが与えられたときの動物が現れる確率( P ( d | c ) )の積の問題に置き換えて考えます。 さらに、訓練データでのクラスの分布が等しいと仮定すれば、 クラスが与えられたときの動物が現れる確率( P ( d | c )だけを考えればいいことになります。 式で書くと以下のように表せます。



実際は、動物の部分は、ここでは16種類の属性に置き換えて、クラスにおけるそれらの 確率に置き換わります。



日本語で書くと以下のようになります。



属性をwに置き換えて、以下のように、クラスごとに各属性の出現確率で近似します。



最終的には、以下の不等式を満たすクラスに分類します。



このように、クラスごと、属性ごとに条件付確率を計算する過程が、 訓練となります。 未知データが与えられたときは、属性ごとに、 2値のブーレアンの場合、未知データの対応する属性がtrueの場合、 Pのままとし、falseの場合、1-Pの確率だったとし、 全体としては、以下のような属性の確率の乗算を計算します。



この値が最も大きくなるクラスに分類されると推定することになります。 なお、このベイズ学習器の場合、属性は「互いに独立している」という仮定をおきます。

条件付確率を計算するConditionalProbability.hの実装例を以下に示します。
ConditionalProbability.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* プログラム6-25 条件付確率計算クラス */
#ifndef _CONDITIONAL_PROBABILITY
#define _CONDITIONAL_PROBABILITY
#include <iostream>
using namespace std;

#define LAPLACE_CORRECTION (0.01)
class ConditionalProbability {
    private:
        int numSo; /* the number of "so" */
        int numAll; /* number of all data */
        double probability; /* (numSo+ε)/(numAll+ε): Laplace smooting */
    public:
        ConditionalProbability(): numSo(0), numAll(0), probability(0.0){}
        ~ConditionalProbability(){}
        void increment(){ numSo++; }/* true事象のときにインクリメント */
        void setAll(int numAll) { this->numAll = numAll; }/* 全事象をセット */
        void compute(){ /* 確率計算の実行:Laplace補正 */
            probability = 
                (numSo+LAPLACE_CORRECTION) / (double)(numAll+LAPLACE_CORRECTION);
        }
        int getNumSo(){ return numSo;}/* trueの数を返す関数数 */
        int getNumAll(){ return numAll; }/* 全事象のを返す関数数 */
        double getP(){ return probability;}/* 確率を返す関数 */
        void setP(double probability){ this->probability = probability;}/* 確率を設定 */
        void init(){ numSo = numAll = 0; probability = 0.0; }/* 初期化関数 */
};
#endif
ここでは、19行目から20行目に、Laplace補正(Laplace correction, Laplace estimator, Laplace smoothing)を使用していることに注意してください。これは、訓練データに、ある属性の発生事象がまったくない場合、確率の積がゼロになることを回避する有名な技法です。
AnimaProbability.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* プログラム6-26 動物学習クラス */
#ifndef _ANIMAL_CONDIOTIONAL_PROBABILITY
#define _ANIMAL_CONDIOTIONAL_PROBABILITY
#include <iostream>
using namespace std;
#include "ConditionalProbability.h"
#include "Attribute.h"

class AnimalProbability  {
    private:
        string kind; /* name of attribute */
        ConditionalProbability hair;/* 髪(毛)があるか */
        ConditionalProbability feather;/* 羽があるか */
        ConditionalProbability egg;/* 卵から生まれるか */
        ConditionalProbability milk; /* 母乳があるか */
        ConditionalProbability airborne; /* 大気中に飛んでいられるか (canFly) */
        ConditionalProbability aquatic; /* 水中で生きていけるか */
        ConditionalProbability predator; /* 捕食者か */
        ConditionalProbability toothed; /* 歯があるか */
        ConditionalProbability backbone; /* 背骨があるか */
        ConditionalProbability breathe;/* 呼吸するか */
        ConditionalProbability venomous;/* 毒があるか */
        ConditionalProbability fin;/* ひれがあるか */
        ConditionalProbability tail; /* しっぽがあるか */
        ConditionalProbability domestic;/* ペットにできるか */
        ConditionalProbability catsize; /* 猫と同程度のサイズか */
        ConditionalProbability legs[6]; /* 足の数 */
    public:
        AnimalProbability(string name): kind(name){
            hair.init(); feather.init(); egg.init(); milk.init(); airborne.init();
            aquatic.init(); predator.init(); toothed.init(); backbone.init();
            breathe.init(); venomous.init(); fin.init();
            tail.init(); domestic.init(); catsize.init();
            for (int i = 0 ; i < 6 ; i++) legs[i].init();
        }
        ConditionalProbability *getHair(){ return &hair; }
        ConditionalProbability *getFeather(){ return &feather; }
        ConditionalProbability *getEgg(){ return &egg; }
        ConditionalProbability *getMilk(){ return &milk; }
        ConditionalProbability *getAirborne(){ return &airborne; }
        ConditionalProbability *getAquatic(){ return &aquatic; }
        ConditionalProbability *getPredator(){ return &predator; }
        ConditionalProbability *getToothed(){ return &toothed;}
        ConditionalProbability *getBackbone(){ return &backbone;}
        ConditionalProbability *getBreathe(){ return &breathe;}
        ConditionalProbability *getVenomous(){ return &venomous;}
        ConditionalProbability *getFin(){ return &fin; }
        ConditionalProbability *getTail(){ return &tail; }
        ConditionalProbability *getDomestic(){ return &domestic; }
        ConditionalProbability *getCatsize(){ return &catsize;}
        ConditionalProbability *getLegs0(){ return &legs[0]; }
        ConditionalProbability *getLegs2(){ return &legs[1]; }
        ConditionalProbability *getLegs4(){ return &legs[2]; }
        ConditionalProbability *getLegs6(){ return &legs[3]; }
        ConditionalProbability *getLegs8(){ return &legs[4]; }
        ConditionalProbability *getLegsN(){ return &legs[5]; }
    public:
        void doTraining(Attribute *atr){/* 訓練:trueならインクリメント */
            ConditionalProbability *bp;
            bp = getHair();if (atr->hasHair) bp->increment();/* hair */     
            bp = getFeather();if (atr->hasFeather) bp->increment();/* feather */ 
            bp = getEgg();if (atr->hasEgg) bp->increment();/* egg */ 
            bp = getMilk();if (atr->hasMilk) bp->increment();/* milk */ 
            bp = getAirborne(); if (atr->isAirborne) bp->increment();/* airborne */
            bp = getAquatic();if (atr->isAquatic) bp->increment();/* aquatic */ 
            bp = getPredator();if (atr->isPredator) bp->increment();/* predator */ 
            bp = getToothed();if (atr->isToothed) bp->increment();/* toothed */ 
            bp = getBackbone();if (atr->hasBackbone) bp->increment();/* backbone */ 
            bp = getBreathe();if (atr->doesBreathe) bp->increment();/* breathe */ 
            bp = getVenomous();if (atr->isVenomous) bp->increment();/* venomous */ 
            bp = getFin();if (atr->hasFin) bp->increment();/* fin */ 
            bp = getTail();if (atr->hasTail) bp->increment();/* tail */ 
            bp = getDomestic();if (atr->isDomestic) bp->increment();/* domestic */ 
            bp = getCatsize();if (atr->isCatsize) bp->increment();/* catsize */ 

            bp = getLegs0(); if (atr->numLegs[0]) bp->increment();
            bp = getLegs2(); if (atr->numLegs[1]) bp->increment();
            bp = getLegs4(); if (atr->numLegs[2]) bp->increment();
            bp = getLegs6(); if (atr->numLegs[3]) bp->increment();
            bp = getLegs8(); if (atr->numLegs[4]) bp->increment();
            bp = getLegsN(); if (atr->numLegs[5]) bp->increment();
        }
        void doPostTraining(Attribute *atr, int size){/* 出現確率の計算 */
            ConditionalProbability *bp;
            bp = getHair(); bp->setAll(size); bp->compute(); /* hair */ 
            bp = getFeather(); bp->setAll(size); bp->compute();/* feather */ 
            bp = getEgg(); bp->setAll(size); bp->compute(); /* egg */ 
            bp = getMilk(); bp->setAll(size); bp->compute();/* milk */ 
            bp = getAirborne(); bp->setAll(size); bp->compute();/* airborne */ 
            bp = getAquatic(); bp->setAll(size); bp->compute();/* aquatic */ 
            bp = getPredator(); bp->setAll(size); bp->compute();/* predator */ 
            bp = getToothed(); bp->setAll(size); bp->compute();/* toothed */ 
            bp = getBackbone(); bp->setAll(size); bp->compute();/* backbone */ 
            bp = getBreathe(); bp->setAll(size); bp->compute();/* breathe */ 
            bp = getVenomous(); bp->setAll(size); bp->compute();/* venomous */ 
            bp = getFin(); bp->setAll(size); bp->compute();/* fin */ 
            bp = getTail(); bp->setAll(size); bp->compute();/* tail */ 
            bp = getDomestic(); bp->setAll(size); bp->compute();/* domestic */ 
            bp = getCatsize(); bp->setAll(size); bp->compute();/* catsize */ 
            bp = getLegs0(); bp->setAll(size); bp->compute();
            bp = getLegs2(); bp->setAll(size); bp->compute();
            bp = getLegs4(); bp->setAll(size); bp->compute();
            bp = getLegs6(); bp->setAll(size); bp->compute();
            bp = getLegs8(); bp->setAll(size); bp->compute();
            bp = getLegsN(); bp->setAll(size); bp->compute();
        }
        double estimate(Attribute *atr){/* ここで確率の乗算を行う */
            ConditionalProbability *bp;
            double value = 1.0;
            bp = getHair();/* hair */ 
            if (atr->hasHair) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getFeather();/* feather */ 
            if (atr->hasFeather) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getEgg();/* egg */ 
            if (atr->hasEgg) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getMilk();/* milk */ 
            if (atr->hasMilk) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getAirborne();/* airbone */ 
            if (atr->isAirborne) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getAquatic();/* aquatic */ 
            if (atr->isAquatic) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getPredator();/* predator */ 
            if (atr->isPredator) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getToothed();/* toothed */ 
            if (atr->isToothed) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getBackbone();/* backnone */ 
            if (atr->hasBackbone) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getBreathe();/* breathe */ 
            if (atr->doesBreathe) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getVenomous();/* venomous */ 
            if (atr->isVenomous) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getFin();/* fin */ 
            if (atr->hasFin) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getTail();/* tail */ 
            if (atr->hasTail) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getDomestic();/* domestic */ 
            if (atr->isDomestic) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getCatsize();/* catsize */ 
            if (atr->isCatsize) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getLegs0();/* numLegs */ 
            if (atr->numLegs[0]) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getLegs2();/* numLegs */ 
            if (atr->numLegs[1]) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getLegs4();/* numLegs */ 
            if (atr->numLegs[2]) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getLegs6();/* numLegs */ 
            if (atr->numLegs[3]) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getLegs8();/* numLegs */ 
            if (atr->numLegs[4]) value *= bp->getP();else value *= (1 - bp->getP()); 
            bp = getLegsN();/* numLegs */ 
            if (atr->numLegs[5]) value *= bp->getP();else value *= (1 - bp->getP()); 
            
            return value;
        }

};
#endif
訓練時に実行するのは、以下のプログラムで、 基底クラスのポインタを通して派生クラスのプログラムを呼び出す形となります。
Training.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* プログラム6-27 main関数から呼び出される訓練用の関数 */
#include "Attribute.h"
#include "AnimalHeader.h"
#include "AnimalProbability.h"
typedef vector <Animal*> AnimVector;

void training ( AnimalProbability *data, AnimVector &vec ){
	AnimalProbability *ap;/* 各種の動物の条件付確率を計算するクラスへのポインタ */
	int size = (int)vec.size();/* 訓練した可変長データのサイズ */
	for (int i = 0 ; i < size ; i++){
		Animal *p = vec[i];/* 基底クラスのポインタ:実際は各、派生クラス */
		#ifdef DEBUG
		p->print(); /* デバッグ時のプリント */
		#endif
		ap = data;/* 各派生クラスのデータ */
		ap->doTraining(p->getA());/* 訓練データから頻度計算 */
	}
	Animal *p = vec[size-1];
	ap = data;/* 各派生クラスのデータ */
	ap->doPostTraining(p->getA(), size);/* 条件付確率の計算 */
}
上のプログラムで使用されているAnimalHeader.hは以下のような内容です。
AnimalHeader.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
/* プログラム6-28 Training.cppとEstimateAnimal.cppで使われるヘッダーファイル */
using namespace std;
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "Mammal.h" /* 哺乳類 */
#include "Reptile.h" /* 爬虫類 */
#include "Amphibian.h"/* 両生類 */
#include "Bird.h" /* 鳥類 */
#include "Fish.h" /* 魚類 */
#include "Insect.h" /* 昆虫 */
#include "Invertebrate.h" /* 無脊椎動物 */

#include <cstdlib>
#include <vector> /* STL: vector */
#include <cmath>
訓練用のデータの読み込みは、以下のようなプログラムで行っています。
FileLoader.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* プログラム6-29 FileLoaderで使われる関数 */
#include "Attribute.h"
#include "AnimalHeader.h"
#include "AnimalProbability.h"
typedef vector <Animal*> AnimVector;

string trim(const string& str,  const string& whitespace = " \t"){
    /* 文字列の両端のspaceを削除 */
    const size_t strBegin = str.find_first_not_of(whitespace);
    if (strBegin == string::npos)  return ""; 
    const size_t strEnd = str.find_last_not_of(whitespace);
    const size_t strRange = strEnd - strBegin + 1;
    return str.substr(strBegin, strRange);
}

/**
* split関数
* @param string str 分割したい文字列
* @param string delim デリミタ
* @return vector <string> 分割された文字列
*/
vector <string> split ( string str, string delim )
{
    vector <string> result;/* 結果保持用のSTLの線形リスト */
    string::size_type pos;/* デリミターで切断する位置 */
    while ( (pos = str.find_first_of(delim)) != string::npos ){
        if ( pos != string::npos ){/* 見つかった場合 */
            string tmp = str.substr(0, pos);
            trim(tmp);
            result.push_back(tmp);/* 部分文字列をリストに */
        }
        str = str.substr(pos + 1);/*残りの文字列 */
    }
    if (str.length() > 0) {
        result.push_back(str);
    }
    return result;
}
FileLoader.cpp本体は以下のようです。
FileLoader.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* プログラム6-30 動物の訓練データファイルの読み込み関数 */
#include "FileLoader.h"

void fileLoader(ifstream &fin, AnimVector &mVec,
    AnimVector &rVec, AnimVector &aVec,
    AnimVector &bVec, AnimVector &fVec,
    AnimVector &sVec, AnimVector &iVec)
{
    
    string name, kind;
    bool hasHair;/* 髪(毛)があるか */
    bool hasFeather;/* 羽があるか */
    bool hasEgg;/* 卵から生まれるか */
    bool hasMilk; /* 母乳があるか */
    bool isAirborne; /* 大気中に飛んでいられるか (canFly) */
    bool isAquatic; /* 水中で生きていけるか */
    bool isPredator; /* 捕食者か */
    bool isToothed; /* 歯があるか */
    bool hasBackbone; /* 背骨があるか */
    bool doesBreathe;/* 呼吸するか */
    bool isVenomous;/* 毒があるか */
    bool hasFin;/* ひれがあるか */
    bool numLegs[6]; /* 足の数 */
    bool hasTail; /* しっぽがあるか */
    bool isDomestic;/* ペットにできるか */
    bool isCatsize; /* 猫と同程度のサイズか */

    string line;
    Mammal *mammal;/* 哺乳類用の変数 */
    Reptile *reptile;/* 爬虫類用の変数 */
    Amphibian *amphibian;/* 両生類用の変数 */
    Bird *bird;/* 鳥類用の変数 */
    Fish *fish;/* 魚類用の変数 */
    Insect *insect;/* 昆虫類用の変数 */
    Invertebrate *invertebrate;/* 無脊椎動物用の変数 */

    /* skip the first line */
    getline(fin, line);/* 1行目をスキップ */

    vector <string> result;/* split関数の結果を保持 */

    while (fin.good()){
        if (!getline(fin, line)) break;/* 行単位に読み、EOFならbreak */
        istringstream ss(line);
        
        result = split(line,",");/* split関数で分離 */
        vector <string> ::iterator it = result.begin(); 
        name = *it; name = trim(name," \t\n\r"); it++;/* 名前の取り出し */
        hasHair = atoi((*it).c_str()) == 1 ? true : false; it++;
        hasFeather = atoi((*it).c_str()) == 1 ? true : false; it++;
        hasEgg = atoi((*it).c_str()) == 1 ? true : false; it++;
        hasMilk = atoi((*it).c_str()) == 1 ? true : false; it++;
        isAirborne = atoi((*it).c_str()) == 1 ? true : false; it++;
        isAquatic = atoi((*it).c_str()) == 1 ? true : false; it++;
        isPredator = atoi((*it).c_str()) == 1 ? true : false; it++;
        isToothed = atoi((*it).c_str()) == 1 ? true : false; it++;
        hasBackbone = atoi((*it).c_str()) == 1 ? true : false; it++;
        doesBreathe = atoi((*it).c_str()) == 1 ? true : false; it++;
        isVenomous = atoi((*it).c_str()) == 1 ? true : false; it++;
        hasFin = atoi((*it).c_str()) == 1 ? true : false; it++;
        int nLegs = atoi((*it).c_str()); it++;
        for (int i = 0 ; i < 6 ; i++ ) numLegs[i] = false;
        if (nLegs == 0)          numLegs[0] = true;
        else if (nLegs == 2)     numLegs[1] = true;
        else if (nLegs == 4)     numLegs[2] = true;
        else if (nLegs == 6)     numLegs[3] = true;
        else if (nLegs == 8)     numLegs[4] = true;
        else                     numLegs[5] = true;
        hasTail = atoi((*it).c_str()) == 1 ? true : false; it++;
        isDomestic = atoi((*it).c_str()) == 1 ? true : false; it++;
        isCatsize = atoi((*it).c_str()) == 1 ? true : false; it++;
        kind = *it; kind = trim(kind," \t\n\r"); it++;/* 種類の取り出し */

        Attribute *a = new Attribute(name, hasHair, hasFeather, hasEgg,
            hasMilk, isAirborne, isAquatic, isPredator, isToothed, 
            hasBackbone, doesBreathe, isVenomous, hasFin, 
            numLegs, hasTail, isDomestic, isCatsize);/* 属性オブジェクト生成 */

        /* 入力時は派生クラスでオブジェクトを作成し、
      できたオブジェクトは vector <Animal*>型のvectorで保持する */
        if (kind == "mammal"){/* 哺乳類クラスの場合 */
            mammal = new Mammal(*a);/* 哺乳類クラスのオブジェクト作成 */
            mVec.push_back(mammal);/* 哺乳類のリストに追加 */ 
        }
        else if (kind == "reptile"){/* 爬虫類クラスの場合 */
            reptile = new Reptile(*a);/* 爬虫類クラスのオブジェクト作成 */
            rVec.push_back(reptile);/* 爬虫類のリストに追加 */ 
        }
        else if (kind == "amphibian"){/* 両生類クラスの場合 */
            amphibian = new Amphibian(*a);/* 両生類クラスのオブジェクト作成 */
            aVec.push_back(amphibian);/* 両生類のリストに追加 */ 
        }
        else if (kind == "bird"){/* 鳥類クラスの場合 */
            bird = new Bird(*a);/* 鳥類クラスのオブジェクト作成 */
            bVec.push_back(bird);/* 鳥類のリストに追加 */ 
        }
        else if (kind == "fish"){/* 魚類クラスの場合 */
            fish = new Fish(*a);/* 魚類クラスのオブジェクト作成 */
            fVec.push_back(fish);/* 魚類のリストに追加 */ 
        }
        else if (kind == "insect"){/* 昆虫クラスの場合 */
            insect = new Insect(*a);/* 昆虫クラスのオブジェクト作成 */
            sVec.push_back(insect);/* 昆虫のリストに追加 */ 
        }
        else if (kind == "invertebrate"){/* 無脊椎動物クラスの場合 */
            invertebrate = new Invertebrate(*a);/* 無脊椎動物クラスのオブジェクト作成 */
            iVec.push_back(invertebrate);/* 無脊椎動物のリストに追加 */ 
        }
        else {
        }
    }
    fin.close();/* ファイルのクローズ */
}
main関数は以下のようです。
EstimateAnimal.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/* プログラム6-31 main関数 訓練から未知データの推定まで */
#include "Attribute.h"
#include "AnimalHeader.h"
#include "AnimalProbability.h"
typedef vector <Animal*> AnimVector;

void fileLoader(ifstream &fin,	AnimVector &mVec, AnimVector &rVec, AnimVector &aVec, 
    AnimVector &bVec, AnimVector &fVec, AnimVector &sVec, AnimVector &iVec);
void training(AnimalProbability *data, AnimVector &vec);

int main(int ac, char **av){
    if (ac != 2){
        cout << "EstimateAnimal  ファイル名" << endl;
        return 1;
    }
    char *fn = av[1];/* csvファイル */
    ifstream fin(fn, ios::in );
    if (fin.fail()){
        cerr << "ファイル" << av[1] << "がオープンできません" << endl;
        return 1;
    }
    bool hasHair;/* 髪(毛)があるか */
    bool hasFeather;/* 羽があるか */
    bool hasEgg;/* 卵から生まれるか */
    bool hasMilk; /* 母乳があるか */
    bool isAirborne; /* 大気中に飛んでいられるか (canFly) */
    bool isAquatic; /* 水中で生きていけるか */
    bool isPredator; /* 捕食者か */
    bool isToothed; /* 歯があるか */
    bool hasBackbone; /* 背骨があるか */
    bool doesBreathe;/* 呼吸するか */
    bool isVenomous;/* 毒があるか */
    bool hasFin;/* ひれがあるか */
    bool numLegs[6]; /* 足の数 */
    bool hasTail; /* しっぽがあるか */
    bool isDomestic;/* ペットにできるか */
    bool isCatsize; /* 猫と同程度のサイズか */

    AnimVector mVec; mVec.resize(0);/* 哺乳類のリスト */
    AnimVector rVec; rVec.resize(0);/* 爬虫類のリスト */
    AnimVector aVec; aVec.resize(0);/* 両生類のリスト */
    AnimVector bVec; bVec.resize(0);/* 鳥類のリスト */
    AnimVector fVec; fVec.resize(0);/* 魚類のリスト */
    AnimVector sVec; sVec.resize(0);/* 昆虫のリスト */
    AnimVector iVec; iVec.resize(0);/* 無脊椎動物のリスト */

    fileLoader(fin, mVec, rVec, aVec, bVec, fVec, sVec, iVec);/* 訓練データ読込み*/

    /* ベイズ学習のためのデータ保持 */
    AnimalProbability mData("mammal"), rData("reptile"), aData("amphibian"),
        bData("bird"), fData("fish"), sData("insect"), iData("invertebrate");

    /* 種類ごとの入力データの確認:訓練ステージ */
    training(&mData, mVec); /* 哺乳類 */
    training(&rData, rVec); /* 爬虫類 */
    training(&aData, aVec); /* 両生類 */
    training(&bData, bVec); /* 鳥類 */
    training(&fData, fVec); /* 魚類 */
    training(&sData, sVec); /* 昆虫 */
    training(&iData, iVec); /* 無脊椎動物 */

    /* 特定の動物のカテゴリーをベイズ学習器で予測 */ 
    for (int i = 0 ; i < 6 ; i++ ) numLegs[i] = false;
    numLegs[2] = true; /* 4本 */
    Attribute *unknown = new Attribute("サンショウウオ", hasHair=false, 
        hasFeather=false, hasEgg=true, hasMilk=false, 
        isAirborne=false, isAquatic=true, isPredator=true, 
        isToothed=true, hasBackbone=true, doesBreathe=true, 
        isVenomous=false, hasFin=true, numLegs, 
        hasTail=false, isDomestic=false, isCatsize=false);
    unknown->print();/* 未知データのプリント */

    double value, max = -9999.0;
    int winner = -1;
    AnimalProbability *ap;

    /* 哺乳類 check */    ap = &mData; value = ap->estimate(unknown);
    if (value > max) { winner = 0; max = value;}
    /* 爬虫類 check */    ap = &rData; value = ap->estimate(unknown);
    if (value > max) { winner = 1; max = value;}
    /* 両生類 check */    ap = &aData; value = ap->estimate(unknown);
    if (value > max) { winner = 2; max = value;}
    /* 鳥類 check */    ap = &bData; value = ap->estimate(unknown);
    if (value > max) { winner = 3; max = value;}
    /* 魚類 check */    ap = &fData; value = ap->estimate(unknown);
    if (value > max) { winner = 4; max = value;}
    /* 昆虫 check */    ap = &sData; value = ap->estimate(unknown);
    if (value > max) { winner = 5; max = value;}
    /* 無脊椎動物 check */    ap = &iData; value = ap->estimate(unknown);
    if (value > max) { winner = 6; max = value;}

    cout << "***** ナイーブベイズ学習で未知の動物の分類を推定 *****" << endl;
    cout << "サンショウウオは、";
    switch(winner){
        case 0:{ cout << "★哺乳類★と推測されます" << endl; break;}
        case 1:{ cout << "★爬虫類★と推測されます" << endl; break;}
        case 2:{ cout << "★両生類★と推測されます" << endl; break;}
        case 3:{ cout << "★鳥類★と推測されます" << endl; break;}
        case 4:{ cout << "★魚類★と推測されます" << endl; break;}
        case 5:{ cout << "★昆虫★と推測されます" << endl; break;}
        case 6:{ cout << "★無脊椎動物★と推測されます" << endl; break;}
    }
    cout << "マルチノミアル積値(負対数)は" << (-log(max)) << "です" << endl;
    return 0;
}
以下がコンパイルと実行例です。

$ make
g++ -Wall -std=c++14 -c EstimateAnimal.cpp
g++ -Wall -std=c++14 -c Training.cpp
g++ -Wall -std=c++14 -c FileLoader.cpp
g++ -Wall -std=c++14 -o EstimateAnimal EstimateAnimal.o Training.o FileLoader.o

$ make run
./EstimateAnimal animal.csv
名前 = サンショウウオ
        毛 = false
        羽 = false
        卵 = true
        母乳 = false
        飛行 = false
        水生 = true
        肉食 = true
        歯 = true
        背骨 = true
        呼吸 = true
        毒 = false
        ひれ = true
        しっぽ = false
        家畜・ペット = false
        猫と同サイズ = false
        足の数 = 4
***** ナイーブベイズ学習で未知の動物の分類を推定 *****
サンショウウオは、★両生類★と推測されます
マルチノミアル積値(負対数)は6.88864です

C++11以降に導入された機能(代表例)

C++11以降に導入された幾つかの機能を紹介します。

リストによる初期化

たとえば、

    int x1 = 100;  //C++98
    int x2(100);   //C++98
    int x3 = {100};//C++11
    int x3 {100};  //C++11
はいずれも、100という整数値を初期化するもので、後半の2つがC++11から導入されました。 一方、
    auto x1 = 100;  //C++98
    auto x2(100);   //C++98
    auto x3 = {100};//C++11
    auto x3 {100};  //C++11
のように、auto型を使って型の演繹(type deduction)が、 あたかも、テンプレートと似たように利用できる機能がC++11から導入されました。 上記の4つのauto型の演繹では、intの場合と意味が違ってきます。特に、後半の2つは、 std::initializer_list<int>型でリストがひとつの要素100からなる、データ型であると解釈されます。 なお、auto型は、関数の型の定義、ループ内の変数、 もしくは、初期化される変数の型や、constconstexprと一緒に使うことが通常です。 重要なのは、型が演繹できることです。従って、たとえばint i;の代わりに、auto i;とはできません。 理由は、型が演繹(類推)できないからです。

Move Semantics

C++11の大きな特徴のひとつが、 『CopyでなくMoveを使って効率よいコンパイラでのコード生成をしよう』 という機能です。 たとえば、テンプレートを使ったSwap関数を考えると、 C++98までのやり方では、

    template <typename T>
    void swap(T& a, T& b){
    	T tmp {a}; // T tmp = a;と同じ意味
    	a = b;
    	b = tmp;
    }
という書法です。 しかし、この場合、引数がCopyされてしまい、 Tの型によっては、とても重たいコードが生成されてしまします。 これに代わり、C++11では、 以下のようにmove(x)を用いて、Copyすることなく、 Moveすることで、効率のよいコード生成ができるようになっています。 これをMove Semanticsと呼ぶことがあります。
    template <typename T>
    void swap(T& a, T& b){
    	T tmp {move(a)}; // move from a
    	a = move(b);     // move from b
    	b = move(tmp);   // move from tmp
    }
実際はmove(x) = static_cast<T&&>(x) で実装されています。コンパイラはCopyすることなく実現できるため、 効率よいプログラムのためにMove Semanticsは重要とされています。