タイトル通り、LL系言語(Python, Perl, Luaあたり)しか使ってこなかったゆとりプログラマが、C++ に手を出そうとしたときに調べたことなどをとめどもなく書いていきます。
インスタンスの生成方法は 2 通りある
void hoge()
{
foo::Foo f = foo::Foo();
f.hello();
...
}
void hoge()
{
foo::Foo* f = new foo::Foo();
f->hello();
...
delete f;
}
前者の方法だとインスタンスはメモリのスタック領域に格納され、関数 hoge()
から抜ける際に自動的に破棄される。後者の方法ではインスタンスはヒープ領域に格納され、明示的に delete
を呼ぶまで破棄されない。return
直前で delete
を呼んでいても、例外等でそこまでたどり着かない場合もあるので要注意。
クラス継承の定石
namespace foo {
class CBase {
public:
CBase();
virtual void hello() = 0;
};
class Foo : public CBase {
public:
Foo();
void hello();
};
}
override する前提のメソッドは、基底クラス側の宣言時に接頭辞 virtual を付けて、0 を代入しておく。override し忘れるとエラー。
基本クラスのメンバへのアクセス制限
public
namespace foo {
class CBase {
public:
CBase() {};
int x = 10;
protected:
int y = 20;
private:
int z = 30;
};
class Foo : public CBase {
public:
Foo() {};
void foo() {
this->x; // public メンバとしてアクセスできる
this->y; // protected メンバとしてアクセスできる
// this->z; // アクセスできない
};
};
class Bar : public Foo {
public:
Bar() {};
void bar() {
this->x; // public メンバとしてアクセスできる
this->y; // protected メンバとしてアクセスできる
// this->z; // アクセスできない
};
};
}
foo::Foo f = foo::Foo();
f.x; // アクセスできる
// f.y // アクセスできない
// f.z // アクセスできない
foo::Bar b = foo::Bar();
b.x; // アクセスできる
// b.y // アクセスできない
// b.z // アクセスできない
CBase を Foo が public 継承する場合。CBase で public 宣言されているメンバは Foo にとっても Bar にとっても public。CBase で protected 宣言されているメンバは Foo にとっても Bar にとっても protected。CBase で private 宣言されているメンバは Foo からも Bar からも見えない。
protected
namespace foo {
class CBase {
public:
CBase() {};
int x = 10;
protected:
int y = 20;
private:
int z = 30;
};
class Foo : protected CBase {
public:
Foo() {};
void foo() {
this->x; // protected メンバとしてアクセスできる
this->y; // protected メンバとしてアクセスできる
// this->z; // アクセスできない
};
};
class Bar : public Foo {
public:
Bar() {};
void bar() {
this->x; // protected メンバとしてアクセスできる
this->y; // protected メンバとしてアクセスできる
// this->z; // アクセスできない
};
};
}
foo::Foo f = foo::Foo();
// f.x; // アクセスできない
// f.y; // アクセスできない
// f.z; // アクセスできない
foo::Bar b = foo::Bar();
// b.x; // アクセスできない
// b.y; // アクセスできない
// b.z; // アクセスできない
CBase を Foo が protected 継承する場合。CBase で public 宣言されているメンバ x は Foo からは protected に制限されて見える。それに伴って、Foo を public 継承している Bar からも x は protected メンバとして見える。CBase で protected 宣言されているメンバについては、先の例と同様に protected メンバとして見える。CBase で private 宣言されているメンバについては当然 Foo からも Bar からも見えない。
private
namespace foo {
class CBase {
public:
CBase() {};
int x = 10;
protected:
int y = 20;
private:
int z = 30;
};
class Foo : private CBase {
public:
Foo() {};
void foo() {
this->x; // private としてアクセスできる
this->y; // private としてアクセスできる
// this->z; // アクセスできない
};
};
class Bar : public Foo {
public:
Bar() {};
void bar() {
// this->x; // アクセスできない
// this->y; // アクセスできない
// this->z; // アクセスできない
};
};
}
foo::Foo f = foo::Foo();
// f.x; // アクセスできない
// f.y; // アクセスできない
// f.z; // アクセスできない
foo::Bar b = foo::Bar();
// b.x; // アクセスできない
// b.y; // アクセスできない
// b.z; // アクセスできない
CBase を Foo が private 継承する場合。CBase で public / protected 宣言されているメンバ x, y は Foo からは private に制限されて見える。それに伴って、Foo を継承している Bar からは x も y も見えなくなる。これは、Foo が CBase を継承していることを派生クラスである Bar に対して隠蔽する効果がある。CBase で private 宣言されているメンバについては当然 Foo からも Bar からも見えない。
基本クラスのコンストラクタ呼び出し
namespace foo {
class CBase {
public:
CBase() {};
CBase(int x) {
this->x = x;
};
private:
int x;
};
class Foo : public CBase {
public:
Foo() {};
Foo(int x) : CBase(x) {
};
};
}
foo::Foo f = foo::Foo(); // 引数なしの Foo::Foo、CBase::CBase が呼ばれる
foo::Foo g = foo::Foo(10); // 引数ありの Foo::Foo、CBase::CBase が呼ばれる
派生クラスのコンストラクタ宣言の後ろに基本クラスのコンストラクタ呼び出し文を書くことで、オーバーロードされた基本クラスのコンストラクタを特定して呼び出すことができる。指定しない場合は引数なしのコンストラクタが呼ばれる。
派生クラスのポインタは基本クラスのポインタにキャストすることで統一して扱うことができる
namespace foo {
class CBase {
public:
...
virtual void hello() = 0;
...
};
class Foo : CBase {
public:
...
void hello();
...
};
class Bar : CBase {
public:
...
void hello();
...
};
}
foo::CBase* a[2];
a[0] = (foo::CBase*)(new foo::Foo()); // 基本クラスのポインタ型にキャスト
a[1] = (foo::CBase*)(new foo::Bar());
for (int i=0; i<2; i++) {
a[i]->hello(); // foo::Foo と foo::Bar を区別することなく、メソッドを呼べる
}
派生クラスのポインタを基本クラスの型にキャストすると派生クラス独自のメンバにはアクセスできなくなる
namespace foo {
class CBase {
public:
CBase();
virtual void common() = 0;
};
class Foo : CBase {
public:
Foo();
void common();
void foo();
};
}
foo::CBase* bp;
foo::Foo* f = new foo::Foo();
f->foo(); // 当然呼べる
bp = (foo::CBase*)f;
bp->common(); // これは foo::CBase で宣言されているので呼べる
//bp->foo(); // こっちは foo::CBase では宣言されていないので呼べない
foo::Foo* f2 = (foo::Foo*)bp;
f2->foo(); // foo::Foo* にキャストするとまた呼べるようになる
テンプレート関数
template <typename T>
T add(T a, T b)
{
return a + b;
}
add((int)10, (int)20);
add((double)1.5, (double)1.2);
型名を T として宣言しておいて、関数定義で実際の型名の代わりに T を使うことで、複数の変数型で共通の関数を利用することができる。
テンプレート宣言 template <typename T>
を書いた直後の関数宣言にテンプレートが適用される。関数宣言 1 つに対してテンプレート宣言は 1 回しか書けないので、複数の型をテンプレートで表すことはできない。従って、add((int)10, (double)1.5)
のような呼び方はできない。
デフォルト引数
int add(int a, int b=0);
int add(int a, int b)
{
return a + b;
}
プロトタイプ宣言か関数実体の定義かどちらか一方に書く。デフォルト値として利用できるのは定数や関数等、プログラム実行前に値が決まっているものだけ。
Python3 ではデフォルト値としてなんでも書けるのだが、mutable な値をデフォルト値として使った場合に、実は最初の1回しか評価されないというハマりどころがあった。
これに関しては C++ のほうが分かりやすいかも。
lambda 式
int x = 10;
std::function<int(int, int)> lambda_func = [](int a, int b){ return a + b; };
int res = lambda_func(10, 20); // res = 30
この場合、ローカル変数 x
は lambda 式の内部からはアクセスできない。x
を lambda 式内部から参照したい場合は以下の変数キャプチャ(値キャプチャ)を指定する。
int x = 10;
std::function<int(int, int)> lambda_func = [=](int a, int b){ return x + a + b; };
int res = lambda_func(10, 20); // res = 40
変数キャプチャに [=]
を指定すると、ローカル変数のコピーが lambda 式内部で参照可能になる。ただし値の変更を行うことはできない。lambda 式内部でローカル変数の値を変更したい場合は以下の変数キャプチャ(参照キャプチャ)を利用する。
int x = 10;
std::function<int(int, int)> lambda_func = [&](int a, int b){ x = 20; return x + a + b; };
int res = lambda_func(10, 20); // res = 50
// x = 20
変数キャプチャに [&]
を指定すると、lambda 式内部の変数 x
は外側の変数 x
とまったく同じものになる。従って、lambda_func
実行後の x
の値は 20 となる。