C++とは

  • C++はC言語にクラス・テンプレート・例外処理などを加えたものです。
  • 標準テンプレートライブラリが存在しなかった頃から初学者に説明する(教える)順番として
    そういうわけで、Cをある程度理解してから勉強してからでないと、なんのこったかワカランと思うのであります。
    という考え方が今でも一般的なようです*1が、Bjarne Stroustrup氏*2の考えは違うようです*3
  • Cは理解したぜ! って人でも、「自分の書いたプログラムのバグが取れねー」とかいっているのなら! もしそーなのなら! C++の勉強する前に、コーヒーブレイクとして、お勧めするサイトがあるのだ。「パソコン初心者の館」の『Cプログラミング診断室』である。大笑いしながら見るのも一興、青ざめながら見るのもまた一興。
  • プログラミングの学び方、教え方も学習者の目的や背景など状況によってどのような学習の仕方が適切なのかは一様ではありませんが、『Bjarne Stroustrup: 標準C++を新しい言語として学ぶ』 や次の項目のリンク先など道標的に参考になるかと思います。
  • 標準C++の歴史と哲学(豊田孝の「IT談話館」)

インストール

インストールはC言語のページを参照して欲しい

C言語との(クラス・テンプレート・例外処理以外の)違い

関数のプロトタイプ

C :引数を取らない場合、voidを明示しなければならない
C++:引数を取らない場合、何も記載しないでも良い(voidを明示しても良い)
C  :関数のプロトタイプ宣言はしなくてもよい
C++:別ファイル内や呼出し元よりも下で関数が定義されている場合、プロトタイプ宣言が必須

戻り値

C :戻り値がある場合、値を返さなくてもコンパイル可能
C++:戻り値がある場合、必ず何らかの値を返さないとコンパイルエラー

変数の宣言位置

C :関数などのブロックの先頭で宣言しなければならない(注)
C++:任意の位置で宣言可能
(注)C99規格対応により、任意位置での宣言がサポートされた

for文

C++:for文の初期化部分で変数が宣言可能、かつ、スコープはループ内(注),(注2)
(注)VC2003の場合、"言語拡張"オプションで変更可能、かつ、デフォルトでは、スコープはループ内に収まらないので注意。
(注2)VC6の場合、スコープはループ内に収まらず、かつオプションで変更も不可能である。

(例)

for ( int i = 0 ; i < 5 ; i++ ){
  printf("%d", i);
}
i++; // ここではiは見えない。エラー。(注に記した場合を除く)

コメント

// による1行コメントが追加(注)
(注)C99規格対応により、Cにも // がサポートされた

新しい型

新しい型として、真偽値を表す bool が追加されました。
代入できる値は true または false のいずれかです。true は真を、falseは偽を表します。

bool b1 = true;
bool b2 = false;

if(b1){ ... } // 実行される

if(!b2){ ... } // 偽の否定は真なのでこれも実行される

if(b2){ ... } // 実行されない

新しい演算子

新しい演算子として、型の情報を返す typeid が追加されました。(詳しくは後述)

キャスト

dynamic_cast、static_cast、reinterpret_cast、const_cast が追加

参照

参照型や、引数の参照渡しが追加(詳しくは後述)

特定キーワードの省略

C++では、struct, enum, classのキーワードは省略できることがある。

enum _sex{male, female, unknown};

struct human{
 int age;
 char* name;
 _sex sex; // Cなら enum _sex sex; と書かねばならないが、enum を省略できる
};

int main(){
 human Taro; // Cなら struct human Taro; と書かねばならないが、struct を省略できる
 return 0;
}

// 引数、返り値の型もキーワードを省略できる
human* set_default(human* hm){
 hm->age = 0; hm->name = NULL; hm->sex = unknown;
 return hm;
}

パラメーター名の省略

C++では、使わないパラメーターは名前を省略することができる。

#include <stdio.h>

int foo(int){ // 使わないパラメーターにわざわざ int dummy などと名前付けしなくてよい
 return 10;
}

int main(){
 printf("%d\n", foo(0));
 return 0;
}

注)これはC++の新機能なのですが、C/C++両用のコンパイラの場合、Cモードでコンパイルしても省略可能な場合が多いです。

新しいキーワード

Cのキーワードに加えて、以下のものが新しくキーワードとして予約されています。

andboolcatchclass
const_castdeletedynamic_castexplicit
falsefriendinlinemutable
namespacenewnotoperator
orprivateprotectedpublic
reinterpret_caststatic_casttemplatethis
throwtruetrytypeid
typenameusingvirtualxor

ただし、論理演算系はVC++6では予約されておらず、通常の識別子として使用できます。(他バージョンのVC++は手元にないので分かりません。情報求む。)

簡単なプログラム

まずは簡単なプログラムの説明をします。 以下のコードは画面に文字の表示をするプログラムでしょう。

#include<iostream>

using namespace std;//stdという名前空間を使用する

int main()//メイン関数。すべての始まり
{
	cout << "Hello,Work" <<endl;//Hello,Workと画面に表示する
	return 0;//システムに0を返す。
}

説明はコメントに書いてあるとおりです Hello,Workと書いてあるところを他の文字列にして試してみてください。 他の文字列を表示することができます。

オブジェクト指向プログラミング

C++はクラス単位でプログラミングを行う言語です。

そもそもクラスって何?という方に簡単な説明をすると クラスは変数や関数を一まとめにしたものです。

何のためにクラスを使うのか?というのを説明すると、 プログラムの変更をしたいとき、その変更を最小限に抑えるためです。 クラスにまとめることによりプログラムそのものを書き換えることなく、クラスを書き換えるだけでプログラムを変更できます。 では、上の文字表示プログラムをクラスを使って書いて見ましょう。 (*注意:実際に文字を表示するだけの小さなものをクラスにまとめるのは馬鹿のすることです。)

#include <iostream>

// PrintCharacterというクラスを宣言
class PrintCharacter { 
public:
	// PrintCharacterというクラスにPrintという関数がありますよということを示す
	void Print( const std::string& str );
};

void PrintCharacter::Print( const std::string& str ) {
	std::cout << str << std::endl; // strを表示
}

int main() {
	// printCharという名前でクラスの実体を生成して使えるようにする
	PrintCharacter printChar;
	// Print関数を使って画面に表示 	
	printChar.Print("Hello,Work");
	return 0;
}

これで画面に表示できます。 かなり意味不明ですね。 次にこれの説明をします。

プログラムの詳細

先ほどの上のプログラムをコンパイルするとHello,Workが表示されると思います。 ではそのプログラムの詳細を示して行きたいと思います。

#include<iostream>

この部分はiostreamというファイルを取り込みます。こうすることでいろいろな関数を使えるようになります。

// PrintCharacterというクラスを宣言
class PrintCharacter {
public:
	// PrintCharacterというクラスにPrintという関数がありますよということを示す
	void Print( const std::string& str );
};

この部分はPrintCharacterというクラスを宣言します。 public:というのはこれをつけることで外からもアクセスできますということを示します。 これがないと外からアクセスできません。

int main() {
	// printCharという名前でクラスの実体を生成して使えるようにする
	PrintCharacter printChar;
	// Print関数を使って画面に表示 	
	printChar.Print("Hello,Work");
	return 0;
}

この部分はメイン関数です。 PPrintCharacter printChar;はPrintCharacterクラスをprintCharという名前で作ります。 printChar.Print("Hello,Work");この部分はprintCharという名前で宣言されたクラスのPrintという関数を呼びます。

これによって文字が表示できます。

関数オーバーロード

同じ名前で中身の違う関数を複数定義できます。
関数をオーバーロードするには、関数の呼び出し時に関数が区別できないと、つまり関数の引数の型が違わないとだめです。

//int 11 を返す関数 f()
int f(){
    return 11;
}

//a+b を返す関数 f(int,int)
int f(int a,int b){
    return a+b;
}

//こういうことも可能です
int f(int a,int b,int c){
    return f()+a+b+c;
}

ただし、無闇に多くの関数をオーバーロードすると乱雑になりやすいので注意しましょう。

演算子のオーバーロード

演算子の機能を、関数としてプログラマが定義できます。
これは、自前で定義したクラスの演算を分かりやすくしたい時などに便利です。 演算子のオーバーロードをするには、キーワードoperatorを使います。

//メートル
class Metre{
public:
    float v;
    Metre(float n){ v = n; }
};

//キロメートル
class KMetre{
public:
    float v;
    KMetre(float n){ v = n; }
};

//メートルとキロメートルの加算
Metre operator + (const Metre &m,const KMetre &km){
    return Metre( m.v + (km.v/1000.0f) );
}

KMetre operator + (const KMetre &km,const Metre &m){
    return KMetre( (m.v*1000.0f) + km.v );
}

//..........

Metre  X(100);
KMetre Y(4);

//クラスは違うが、演算は可能
Metre  Z=X+Y;
KMetre W=Y+X;

参照

参照とは、エイリアスとも呼び、「変数につけられた別名」です。

  • 参照の宣言など
    参照を宣言するには、型名の後に & をつけます。
    例えば、int型変数aの参照bを宣言するには以下のようにします。
    int a = 5;
    int& b = a; // bにaを代入する感じで~
  • 参照の性質
    参照ははじめにも書いた通り、「ある変数の別名」であり、元々の変数名を書いたのとまったく同様に動作します。例えば、
    int a = 5;
    int& b = a;
    b = 3;
    とすると、aの値も3に変わります。( b = 3; の代わりに a = 3; と書いたとの同じ結果を生じる。)
  • 参照の使い道
    これだけだとまったく使い道が無いように思いますが、参照を関数の引数として渡すと便利なことがあります。
    #include <iostream>
    
    void inc(int& x){
     x++;
    }
    
    int main(){
     int a = 5;
     inc(a);
     std::cout << a << std::endl;
    
     return 0; 
    }
    このコードはどんな結果を生じるでしょうか。

    xはint型変数への参照であり、そこにaを渡しているので、 int& x = a; と同じことです。
    つまり、xはaの別名になり、x++; は a++; とまったく同じ挙動をしめします。 すなわち、このプログラムを実行すると 6 が表示されます。
    このようなことはポインタを使えば実現できますが、そうすると (*x)++; としなくてはなりません。
    一階のポインタならば混乱はおきませんが、例えば FILE* へのポインタ(FILE**)を受けて、その指す先に代入・・・などとするとコードが読みにくくなります。
    参照はこのようなことを防ぎ、より直感的に分かりやすいコードを書くことができます。

インライン関数

C++にはインライン関数というものがあります。
簡単に言うと、「(コンパイラが)関数のコードを、その関数を呼び出した場所にそのまま貼り付ける」といった感じです。(通常の関数呼び出しは別の場所にかかれたコードへジャンプする)

~定義方法~
インライン関数を定義するには、関数定義の頭に inline キーワードを付けます。

inline void inc(int* x){ // 定義の頭に inline を指定
 (*x)++;
}

int main(){
 int a = 0;
 inc(&a);
 return 0;
}

これをコンパイルすると、次のコードをコンパイルしたのと(ほぼ)同じアセンブリコードが生成されます。

int main(){
 int a = 0;
 int* x = &a; // この部分が inc(&a) に相当
 (*x)++;      // この部分が inc(&a) に相当
 return 0;
}

ちょうど inc の中身が inc を呼び出した位置に展開された形になり、これを「インライン展開」と呼びます。

~インライン関数のメリット~

  • 通常の関数を使うよりも高速に動作する
    最初にも書いたように、通常の関数呼び出しを行うと、元の関数と別の場所(場所とは生成された実行ファイル上の位置)にあるコードへジャンプします。
    このジャンプを行う際に、現在のレジスタの内容を保存したり、関数の処理が終わって戻ってくる場所を記憶したり、とジャンプの前処理を行う必要があり、これに時間がかかります。
    インライン関数を使うと上に書いたように関数のコードが呼び出し元にそのまま展開されるので、ジャンプの処理は必要なく、より高速に動作します。

~インライン関数のデメリット~

  • コードが肥大化する
    通常の関数呼び出しがなぜ時間のかかる処理をしてまでジャンプをするかと言うと、それは「コードを共有するため」です。
    一度 inc のコードを生成してしまえば、あとは何回 inc が呼び出されようが同じ場所へジャンプすればよいのです。
    しかしインライン関数は関数の中身をそのまま展開するため、100箇所から inc が呼び出されれば同じコードが100箇所に生成されます。
    このためコードが肥大化してしまうというデメリットがあります。

~インライン関数を使う上での注意~

  • inlineを指定した関数が必ずインライン展開されるとは限らない
    コンパイラがインライン展開をするに値する(メリットの方が大きい)と判断しなければ、inlineを指定してもインライン展開されないことがあります。
    (ただし、マイクロソフトのコンパイラでは inline の代わりに __forceinline を指定することで、gccでは inline の代わりに __attribute__((always_inline)) を指定することで強制的にインライン展開させることができます。Borlandのコンパイラにはこのような独自機能はないようです。)
  • inline関数はプロトタイプ宣言できない
    インライン関数は宣言をヘッダに書いて定義は別のファイルに書く といったことができません。
    別ファイルにする場合は .h に直接本体を書いて、それを include する必要があります。

テンプレート関数

テンプレート関数とは、使う側の立場から見れば「引数、返り値の型が決まっていない関数」です。
例えば、xの絶対値を返す関数を作りたいと考えます。
すると、「引数、返り値ともにdouble(数値型で一番ビット幅が大きい)で定義して、キャストして使う」、「関数のオーバーロードを使う」などの方法がうかびます。
前者はどう考えてもスマートではないし、long long(C99で追加)などdouble以上のビット幅をもつ整数型に対応できません。また後者は使いたい型すべてについて同じコードを何回も書かねばなりません。

そこで登場するのがテンプレート関数です。
テンプレート関数を使えば、一度コードを書けばそれをintにもfloatにもdoubleにもlong longにも適用できます。

~テンプレート関数の定義方法~
まず具体例を見て、それから解説します。
xの絶対値を返すテンプレート関数は次のように定義します。

template <typename T> T abs(T x){
 T abs_val;
 if(x >= 0) abs_val = x;
 else abs_val = -1*x;
 return abs_val;
}

まず一行目について

template     // これはテンプレート関数ですよ、という指示
<typename T> // この宣言で以後 T という識別子(名前)は、型の名前(typename)のことですよ、という意味
T            // 返り値の型。
abs(T x)     // 関数の名前、引数の型、引数の名前
{            // 関数定義開始

二行目

T            // 変数の型
abs_val;     // T 型の変数 abs_val を宣言した

あとは見たままで、結局のところ x の絶対値を返します。

この関数を呼び出す時には、

 abs(-1.0);
 abs(-500);

のようにして呼び出します。
するとコンパイラがコンパイルするときに自動で

float abs(float x){ float abs_val; ... }

int abs(int x){ int abs_val; ... }

を生成してくれます。
つまり、コンパイラから見るとテンプレート関数は「コード生成のテンプレートとなる関数」なので、テンプレート関数と呼ばれるわけです。

~補足~
引数の型が決まっていない といっても、T と書いた型はすべて T で共通です(つまり、abs(-500)を呼び出したならその関数内のT型変数はすべてint型になる)
そこで、二種類以上の違った型を引数に取りたいときは次のようにします。

// 型T1のほうが型T2よりもビット幅が大きいなら true 、そうでなければ false を返す
template <typename T1, typename T2> bool bit_large(T1 a, T2 b){
 if( sizeof(a) > sizeof(b) ) return true;
 else return false;
}

int main(){
 char a; int b; double c;
 bit_large(a, b); // T1 は char, T2 は int になり、false が返る
 bit_large(c, b); // T1 は double, T2 は int になり、true が返る
 return 0;
}

また、以下のような特徴があります。

  • インライン関数と同様に、本体と宣言と別々に書くことはできない
  • typename の代わりに class と書いても同じ

ちなみに、最初に定義した abs を独自に定義した型に対して適用するとどうなるでしょうか。
例えば

struct Point{
 int x; int y;
};

int main(){
 Point p = {1, 2};
 abs(p);
 return 0;
}

のように。
可能性は「 x >= 0 でおかしなことが起こり暴走する」か、「そもそもコンパイルが通らない」かのどちらかです。
正解は後者で、暴走したりすることはありません。コンパイラが Point abs(Point x){ ... } を生成し、コンパイルしようと試みて失敗します。
逆に言えば、 Point に int operator >= (int) が定義されているならば、このテンプレート関数は独自に定義した型に対しても適用できるということで、非常に汎用性の高い関数を定義することができます。

テンプレート関数のSpecialize

関数使用時のSpecialize

テンプレート関数は、コンパイラが引数の型にあわせて自動で<typename T>のTを決定しますが、これを強制的に指定することもできます。
次のようにします。

double a = abs<double>(-5);

このとき、引数が-5なので通常は int abs(int x){ ... } が生成されますが、関数名の後に<double>を指定することで強制的に double abs(double x){ ... } を生成させることができます。

関数定義時のSpecialize

また、関数を定義する時に、「int型に対してだけ違う処理をしたい」といったことも可能です。次のようにint型に特化した関数を別につくってやります。

template <typename T> T inc(T x){ // 通常のテンプレート関数
	return x+1; // 通常はx+1を返す
}

template <> int inc(int x){ // int型に特化したテンプレート関数
	return x+2; // int型が与えられたときだけx+2を返す
}

こうしてやると、引数がint型であった場合か、inc<int>(引数)とした場合に、後者の関数が呼び出されます。

ポイントは、

  • template <> を定義のはじめに書く
  • 通常用の関数(template <typename T> ...)よりも後ろに書く

ことです。(template <> ... を前に書くとエラーになる)
これは以下のようにしても実現できそうですが、微妙に動作が異なることがあります。

 template <typename T> T inc(T x){
	return x+1;
}

// int型専用の関数をオーバーロードして作っておく
int inc(int x){
	return x+2;
}

それは、「使用時の特殊化」でやったように、

cout << inc<int>(5) << endl;

とした場合です。この場合、使用するコンパイラによって「通常用のテンプレートで T = int としたものを生成する場合」と、「オーバーロードされたint型の関数を呼び出す場合」があります。
template <> int ... を使えばこのような曖昧さは回避できます。

テンプレートクラス

基礎

テンプレート関数と同様に、テンプレートクラスというものがあります。
これは「メンバの型が実行時に定まるクラス」です。(テンプレート関数は「引数及び返り値の型が実行時に決まる関数」でした。)
次のようにして定義します。

template <typename T>
class Point{
public:
	T x, y;
	Point(T x, T y){
	 this->x = x, this->y = y;
	}
};

template <typename T> の意味はテンプレート関数と同じです。(これはtemplateであって、Tというのは型の名前ですよ、という意味。詳しくはテンプレート関数の項を参照)

このテンプレートクラスを使うには、次のようにします。

#include <iostream>

int main(){
	Point<int> a(1, 2);

	std::cout << a.x << " " << a.y << std::endl;
	return 0;
}

実行結果

1 2

Point型の変数を宣言するときに、Pointの後ろに<型名>をつけることで、クラス定義の中の T が指定した型になります。(上の例では T は int になる。)

またテンプレート関数と同様に、二つ以上の型を取ることもできます。例えば、

template <typename T1, typename T2>
class Point2{
public:
	T1 x;
	T2 y;
	Point2(T1 x, T2 y){
	 this->x = x, this->y = y;
	}
};

として、

#include <iostream>

int main(){
	Point2<int, float> a(1, 2.5);

	std::cout << a.x << " " << a.y << std::endl;
	return 0;
}

のように使えば、

1 2.5

となります。

デフォルトtypename

typenameには、一般の引数と同じように、デフォルトの値を指定することができます。

template <typename T = int> // ここに注目
class Point{
public:
	T x, y;
	Point(T x, T y){
	 this->x = x, this->y = y;
	}
};

上の例のように、デフォルトで指定したい型を<typename T = 型名>として指定しておきます。
このとき、

Point<> a(1, 2);

としてPoint型変数を作ると、Tにはデフォルトのintが入り、

Point<int> a(1, 2);

としたのと同じことになります。(残念ながら Point a(1,2); とはできません。関数でもデフォルト引数があるからといって関数名の後の () を省略できないのと同じ、と思っておきましょう。)

また、次のようなこともできます。

template <typename T1 = int, typename T2 = T1> // T1のデフォルトにint、T2のデフォルトにT1を指定
class Point2{
public:
	T1 x;
	T2 y;
	Point2(T1 x, T2 y){
	 this->x = x, this->y = y;
	}
};

このとき、次の3つは同じ意味です。

Point2<int, int> a(1, 3); // 通常の使い方
Point2<int> a(1, 3);    // T1にintを指定。T2は指定しなければT1と同じになるので、自動的にintになる
Point2<> a(1, 3);      // 両方何も指定しない。T1はデフォルトのintになり、T2はT1と同じになるのでintになる

補足

注意

テンプレートクラスは、テンプレート関数およびインライン関数と同じく、「宣言と実体を別ファイルに書く」ことができません。

メンバ関数の宣言と定義を分けて書く方法

上の注意に書いたように、メンバ関数の宣言と定義を別ファイルに書くことはできませんが、一つのファイル内で別の場所に書くことならできます。
次のようにします。

template <typename T>
class Point{
public:
	T x, y;
	Point(T x, T y); // ここでは宣言のみ
};

temaplate <typename T>     // これが実体を書くときににも必要
Point<T>::Point(T x, T y){ // 通常のクラスなら Point::Point(...){ ... } だが、クラス名の指定に<T>が必要
 this->x = x, this->y = y;
}

もちろん、typenameが二つ以上あればメンバ関数定義のクラス名の指定にもその数だけ書きます。

template <typename T1, typename T2>
class Point2{
public:
	T1 x;
	T2 y;
	Point2(T1 x, T2 y);
};

template <typename T1, typename T2>
Point2<T1, T2>::Point2(T1 x, T2 y){ ... }

STL(StandardTemplateLibrary)

C++には、テンプレートクラスの機能を利用したSTL(StandardTemplateLibrary; スタンダードテンプレートライブラリ)という物があります。
これは何かというと、「便利な機能をまとめたクラスを、テンプレートを使って様々な型に適用できるようにしたもの」です。
例えば、

  • どんな型でも入る(テンプレートの機能)、自由に伸び縮みする配列(クラスを使って実装)のようなもの
  • どんな型でも入る(テンプレートの機能)、ハッシュ表(クラスを使って実装)のようなもの

などがあります。

C++からCの関数を呼び出す方法

C++はCの(ほぼ)上位互換であり、同じコンパイラでコンパイルできます。
しかし、C++からCの関数を呼び出すには注意が必要です。

例えば、 A.c というソースコードと、A.c にある関数が宣言された A.h というヘッダがあったとして、次のような B.cpp を作ったとします。

 #include <iostream>
 #include "A.h"
 
 int main(){
  std::cout << a("foo_bar") << std::endl; // A.hで char* a(char* s); と宣言されているとする
  return 0;
 }

この状態で A.c と B.cpp をプロジェクトに追加して、VisualC++6.0でビルドすると、

 B.obj : error LNK2001: 外部シンボル ""char * __cdecl a(char *)" (?a@@YAPADPAD@Z)" は未解決です

と表示され、ビルドできません。(VC++6.0に限らずどんな開発環境でもビルドできない)

では、これを解決する方法を説明します。

とりあえず解決法

  • A.hの関数プロトタイプ宣言を、 「extern "C" {」 , 「}」 でくくる
  • ただし、こうするとC言語の仕様から外れるので、C++のコードからincludeされたときのみ有効になるように、「#ifdef __cplusplus」 , 「#endif」 を使う

以上をふまえて、修正された A.h は次のようになります。

 #ifdef __cplusplus
  extern "C" {
 #endif
 
 char* a(char* s); // 元々あった宣言文たち
 ......
 
 #ifdef __cplusplus
  }
 #endif

これでめでたくビルドに成功します。

解説

なんでこんなことが必要なのか知りたい人のために解説です。

まず、C、C++のコードはコンパイラによってアセンブリ言語に変換されます。
その際、各関数にあたる部分のコードのはじめには、ラベルという目印が付きます。
(例えば、たいていのCコンパイラなら a という関数のはじめには _a というラベルがつき、a を呼び出す時には call _a などという命令を呼びます。)

このラベルのつけ方が、CとC++で異なるために、extern "C" を使う必要が発生します。
C++では、同じ名前の関数でもオーバーロードが可能なため、 _a という簡単なラベルではなく、関数名、引数の型 の情報を持ったラベル名が使われます。
つまり、 B.cpp の a の呼び出し部分は

 call _acharp

のようになるわけです。(これは例で、実際のラベル名は符号化されてぱっと見ても引数の型がわかるようにはなっていませんが。)
しかし A.c はCモードでコンパイルされているので、関数aのラベルは _a となっており、 _acharp というラベルは見つからずエラーとなります。

では extern "C" はどのような意味があるかというと、「この関数はCのラベル名で探してね」 とコンパイラに指定する働きがあります。
このおかげで B.cpp の a の呼び出し部分が call _acharp ではなく

 call _a

となり、めでたく関数aを見つけることができて呼び出せる、というわけです。

注)以上を考えると、 extern "C" を使わなくても A.c をC++モードでコンパイルすればいいということに気づきます。実際、拡張子を .cpp に変えるかコンパイルオプションでC++としてコンパイルすれば、 A.h を修正しなくてもすみます。しかしあらかじめコンパイル済みのライブラリなどを使う(または配布する)ときにはそうはいかないので、extern "C" を使います。

ほんのり多態性の紹介

例えばこんなクラスを定義(Character.h)したとして

class Character
{
public:
//	virtual void Say() = 0;		// 一身上の都合により純粋仮想関数にはしない
	virtual void Say() {	std::cout << "セリフが無いんじゃよ" << std::endl;}
};
class Hero : public Character
{
public:
	void	Say()	{	std::cout << "悪が栄えた試しなし!" << std::endl;		}
};
class Zako : public Character
{
public:
	void	Say()	{	std::cout << "イー!" << std::endl;	}
};
class Boss : public Character
{
public:
	void	Say()	{	std::cout << "やっておしまい!" << std::endl;	}
};

こんな感じだね。よく初心者サイトに載ってる例は。 でも、これだと、どんなメリットがあるのか良くわかんないんだよね。

#include<iostream>
#include "Character.h"
using namespace std;
int main()
{
	Hero	a;
	Zako	b;
	Boss	c;
	a.Say();
	b.Say();
	c.Say();
	return 0;
}

実行例

c:\>Sample
悪が栄えた試しなし!
イー!
やっておしまい!

ちなみに、こんなことはしちゃだめね。

#include<iostream>
#include<list>
#include "Character.h"
using namespace std;
int main()
{
	Hero	a;
	Zako	b;
	Boss	c;
	list<Character>	aryCharacter;
	aryCharacter.push_back(a);
	aryCharacter.push_back(b);
	aryCharacter.push_back(c);
	for(list<Character>::iterator iter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){
		iter->Say();
	}
	return 0;
}
  • listはSTLに含まれるコンテナで、すさまじく簡単に言ってしまえば動的な配列の一つ。{{br}}
  • iteratorはコンテナにアクセスするためのもので、ポインタみたいに使える。 実行例
    c:\>Sample
    セリフが無いんじゃよ
    セリフが無いんじゃよ
    セリフが無いんじゃよ

動的生成すると、ちゃんと動作するんだけど、メモリリーク対策が面倒くさい。 この例ではたった3行だけどね。

#include<iostream>
#include<list>
#include "Character.h"
using namespace std;
int main()
{
	Hero	a;
	Zako	b;
	Boss	c;
	list<Character*>	aryCharacter;
	aryCharacter.push_back(new Hero);
	aryCharacter.push_back(new Zako);
	aryCharacter.push_back(new Boss);
	for(list<Character*>::iterator iter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){
		(*iter)->Say();
	}
	for(list<Character*>::iterator jter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){
		delete *jter;
	}
	return 0;
}

実行例

c:\>Sample
悪が栄えた試しなし!
イー!
やっておしまい!

そしてメモリリーク対策としてスマートポインタ(boostライブラリのshared_ptr)を導入してみる。 (STLにはスマートポインタとしてauto_ptrがあるけど、コンテナに入れることは出来ない)
できたー! って思ってテストしたらなんか動作がおかしい…もしかしてメモリリーク? って思ったのでは、せっかくの多態性の恩恵も台無しだから、慣れない内・使えない事情が無いならば、使っとけ!

#include<iostream>
#include<list>
#include<boost/shared_ptr.hpp>
#include "Character.h"
using namespace std;
int main()
{
	typedef boost::shared_ptr<Character>	pChar;
	list<pChar>	aryCharacter;
	aryCharacter.push_back(pChar(new Hero));
	aryCharacter.push_back(pChar(new Zako));
	aryCharacter.push_back(pChar(new Boss));
	for(list<pChar>::iterator iter = aryCharacter.begin(); iter != aryCharacter.end(); ++iter){
		iter->get()->Say();
	}
	return 0;
}

実行例

c:\>Sample
悪が栄えた試しなし!
イー!
やっておしまい!

で、こーなってくると、動的生成部分がもーちょっとなんとかならんかなーて思うんだよね。 戻り値がCharacter*な関数があれば、いろいろ便利になる。 しかし面倒くさくなったので、興味がある人は"factory デザインパターン"(abstractを加えても良い)で ググってくれたまえ!

typeid演算子

C++では型の情報を表す新しい演算子、typeid が追加されました。
sizeof と同様にして、typeid(int) や typeid(変数名) のように使えます。

typeid 演算子が返すのは const type_info& であり、type_info クラスには次のメンバがあります(コンストラクタ等は省略)

class type_info {
public:
 bool operator==(const type_info& rhs) const; // 二つの type_info が等しいか
 bool operator!=(const type_info& rhs) const; // 二つの type_info が等しくないか
 int before(const type_info& rhs) const;      // 引数で与えられた type_info が、自分の表す型からの派生型であるか
 const char* name() const;                    // 型の名前
 const char* raw_name() const;                // 型の修飾子付きの名前、ただし符号化されていて人間には読めない
};

typeid は次のように使います。

#include <iostream>
//#include <typeinfo> // type_info クラスが宣言されているが、通常 iostream から読み込み済みであるので不要

using namespace std;

class A{};

int main(){
 A a, aa;
 int b;
	
 cout << typeid(a).name() << endl;
 cout << typeid(a).raw_name() << endl;

 // a の型と aa の型が等しいか?
 if( typeid(a) == typeid(aa) ){
  cout << "typeid(a)==typeid(aa)" <<endl;
 }

 // a の型と b の型が等しいか? 	
 if( typeid(a) == typeid(b) ){
  cout << "typeid(a)==typeid(b)" <<endl;
 }

 return 0;
}

実行結果

class A
.?AVA@@
typeid(a)==typeid(aa)


  • 注意
    • name, raw_name の出力は環境によって変わることがあるので、 strcmp(typename(a).name(), "class A") のようなコードは推奨されません。
    • Microsoftのリファレンスによると、There is no guarantee that type_info::before will yield the same result in different programs or even different runs of the same program.(異なるプログラム中でbeforeメンバが同じ結果を返すという保障はないし、同じプログラムを2回走らせたときでさえその保障はない。)とあるので、 == , != 以外のメンバは使わないことを推奨します。
    • gcc の type_info には、raw_name メンバはありません。

参考サイト

C++規格

STL


*1 参考:CからC++を学ぼうとしないこと
*2 プログラミング言語C++を設計し、最初に実装した人:ビャーネ・ストロヴストルップ - Wikipedia
*3 参考:概念とテクニックを学ぶこと